// Source: https://www.w3.org/TR/html-aria/#allowed-aria-roles-states-and-properties // Source: https://www.w3.org/TR/html-aam-1.0/#html-element-role-mappings // Source https://html.spec.whatwg.org/multipage/dom.html#content-models // Source https://dom.spec.whatwg.org/#dom-element-attachshadow const htmlElms = { a: { // Note: variants work by matching the node against the // `matches` attribute. if the variant matches AND has the // desired property (contentTypes, etc.) then we use it, // otherwise we move on to the next matching variant variant: { href: { matches: '[href]', contentTypes: ['interactive', 'phrasing', 'flow'], allowedRoles: [ 'button', 'checkbox', 'menuitem', 'menuitemcheckbox', 'menuitemradio', 'option', 'radio', 'switch', 'tab', 'treeitem', 'doc-backlink', 'doc-biblioref', 'doc-glossref', 'doc-noteref' ], namingMethods: ['subtreeText'] }, // Note: the default variant is a special variant and is // used as the last match if none of the other variants // match or have the desired attribute default: { contentTypes: ['phrasing', 'flow'], allowedRoles: true } } }, abbr: { contentTypes: ['phrasing', 'flow'], allowedRoles: true }, addres: { contentTypes: ['flow'], allowedRoles: true }, area: { contentTypes: ['phrasing', 'flow'], allowedRoles: false, namingMethods: ['altText'] }, article: { contentTypes: ['sectioning', 'flow'], allowedRoles: [ 'feed', 'presentation', 'none', 'document', 'application', 'main', 'region' ], shadowRoot: true }, aside: { contentTypes: ['sectioning', 'flow'], allowedRoles: [ 'feed', 'note', 'presentation', 'none', 'region', 'search', 'doc-dedication', 'doc-example', 'doc-footnote', 'doc-pullquote', 'doc-tip' ] }, audio: { variant: { controls: { matches: '[controls]', contentTypes: ['interactive', 'embedded', 'phrasing', 'flow'] }, default: { contentTypes: ['embedded', 'phrasing', 'flow'] } }, // Note: if the property applies regardless of variants it is // placed at the top level instead of the default variant allowedRoles: ['application'], chromiumRole: 'Audio' }, b: { contentTypes: ['phrasing', 'flow'], allowedRoles: false }, base: { allowedRoles: false, noAriaAttrs: true }, bdi: { contentTypes: ['phrasing', 'flow'], allowedRoles: true }, bdo: { contentTypes: ['phrasing', 'flow'], allowedRoles: true }, blockquote: { contentTypes: ['flow'], allowedRoles: true, shadowRoot: true }, body: { allowedRoles: false, shadowRoot: true }, br: { contentTypes: ['phrasing', 'flow'], allowedRoles: ['presentation', 'none'], namingMethods: ['titleText', 'singleSpace'] }, button: { contentTypes: ['interactive', 'phrasing', 'flow'], allowedRoles: [ 'checkbox', 'link', 'menuitem', 'menuitemcheckbox', 'menuitemradio', 'option', 'radio', 'switch', 'tab' ], // 5.4 button Element namingMethods: ['subtreeText'] }, canvas: { allowedRoles: true, contentTypes: ['embedded', 'phrasing', 'flow'], chromiumRole: 'Canvas' }, caption: { allowedRoles: false }, cite: { contentTypes: ['phrasing', 'flow'], allowedRoles: true }, code: { contentTypes: ['phrasing', 'flow'], allowedRoles: true }, col: { allowedRoles: false, noAriaAttrs: true }, colgroup: { allowedRoles: false, noAriaAttrs: true }, data: { contentTypes: ['phrasing', 'flow'], allowedRoles: true }, datalist: { contentTypes: ['phrasing', 'flow'], allowedRoles: false, implicitAttrs: { // Note: even though the value of aria-multiselectable is based // on the attributes, we don't currently need to know the // precise value. however, this allows us to make the attribute // future proof in case we ever do need to know it 'aria-multiselectable': 'false' } }, dd: { allowedRoles: false }, del: { contentTypes: ['phrasing', 'flow'], allowedRoles: true }, dfn: { contentTypes: ['phrasing', 'flow'], allowedRoles: true }, details: { contentTypes: ['interactive', 'flow'], allowedRoles: false }, dialog: { contentTypes: ['flow'], allowedRoles: ['alertdialog'] }, div: { contentTypes: ['flow'], allowedRoles: true, shadowRoot: true }, dl: { contentTypes: ['flow'], allowedRoles: ['group', 'list', 'presentation', 'none'], chromiumRole: 'DescriptionList' }, dt: { allowedRoles: ['listitem'] }, em: { contentTypes: ['phrasing', 'flow'], allowedRoles: true }, embed: { contentTypes: ['interactive', 'embedded', 'phrasing', 'flow'], allowedRoles: ['application', 'document', 'img', 'presentation', 'none'], chromiumRole: 'EmbeddedObject' }, fieldset: { contentTypes: ['flow'], allowedRoles: ['none', 'presentation', 'radiogroup'], // 5.5 fieldset and legend Elements namingMethods: ['fieldsetLegendText'] }, figcaption: { allowedRoles: ['group', 'none', 'presentation'] }, figure: { contentTypes: ['flow'], // Note: technically you're allowed no role when a figcaption // descendant, but we can't match that so we'll go with any role allowedRoles: true, // 5.9 figure and figcaption Elements namingMethods: ['figureText', 'titleText'] }, footer: { contentTypes: ['flow'], allowedRoles: ['group', 'none', 'presentation', 'doc-footnote'], shadowRoot: true }, form: { contentTypes: ['flow'], allowedRoles: ['search', 'none', 'presentation'] }, h1: { contentTypes: ['heading', 'flow'], allowedRoles: ['none', 'presentation', 'tab', 'doc-subtitle'], shadowRoot: true, implicitAttrs: { 'aria-level': '1' } }, h2: { contentTypes: ['heading', 'flow'], allowedRoles: ['none', 'presentation', 'tab', 'doc-subtitle'], shadowRoot: true, implicitAttrs: { 'aria-level': '2' } }, h3: { contentTypes: ['heading', 'flow'], allowedRoles: ['none', 'presentation', 'tab', 'doc-subtitle'], shadowRoot: true, implicitAttrs: { 'aria-level': '3' } }, h4: { contentTypes: ['heading', 'flow'], allowedRoles: ['none', 'presentation', 'tab', 'doc-subtitle'], shadowRoot: true, implicitAttrs: { 'aria-level': '4' } }, h5: { contentTypes: ['heading', 'flow'], allowedRoles: ['none', 'presentation', 'tab', 'doc-subtitle'], shadowRoot: true, implicitAttrs: { 'aria-level': '5' } }, h6: { contentTypes: ['heading', 'flow'], allowedRoles: ['none', 'presentation', 'tab', 'doc-subtitle'], shadowRoot: true, implicitAttrs: { 'aria-level': '6' } }, head: { allowedRoles: false, noAriaAttrs: true }, header: { contentTypes: ['flow'], allowedRoles: ['group', 'none', 'presentation', 'doc-footnote'], shadowRoot: true }, hgroup: { contentTypes: ['heading', 'flow'], allowedRoles: true }, hr: { contentTypes: ['flow'], allowedRoles: ['none', 'presentation', 'doc-pagebreak'], namingMethods: ['titleText', 'singleSpace'] }, html: { allowedRoles: false, noAriaAttrs: true }, i: { contentTypes: ['phrasing', 'flow'], allowedRoles: true }, iframe: { contentTypes: ['interactive', 'embedded', 'phrasing', 'flow'], allowedRoles: ['application', 'document', 'img', 'none', 'presentation'], chromiumRole: 'Iframe' }, img: { variant: { nonEmptyAlt: { matches: { attributes: { alt: '/.+/' } }, allowedRoles: [ 'button', 'checkbox', 'link', 'menuitem', 'menuitemcheckbox', 'menuitemradio', 'option', 'progressbar', 'scrollbar', 'separator', 'slider', 'switch', 'tab', 'treeitem', 'doc-cover' ] }, usemap: { matches: '[usemap]', contentTypes: ['interactive', 'embedded', 'phrasing', 'flow'] }, default: { // Note: allow role presentation and none on image with no // alt as a way to prevent axe from flagging the image as // needing an alt allowedRoles: ['presentation', 'none'], contentTypes: ['embedded', 'phrasing', 'flow'] } }, // 5.10 img Element namingMethods: ['altText'] }, input: { variant: { button: { matches: { properties: { type: 'button' } }, allowedRoles: [ 'link', 'menuitem', 'menuitemcheckbox', 'menuitemradio', 'option', 'radio', 'switch', 'tab' ] }, // 5.2 input type="button", input type="submit" and input type="reset" buttonType: { matches: { properties: { type: ['button', 'submit', 'reset'] } }, namingMethods: ['valueText', 'titleText', 'buttonDefaultText'] }, checkboxPressed: { matches: { properties: { type: 'checkbox' }, attributes: { 'aria-pressed': '/.*/' } }, allowedRoles: ['button', 'menuitemcheckbox', 'option', 'switch'], implicitAttrs: { 'aria-checked': 'false' } }, checkbox: { matches: { properties: { type: 'checkbox' }, attributes: { 'aria-pressed': null } }, allowedRoles: ['menuitemcheckbox', 'option', 'switch'], implicitAttrs: { 'aria-checked': 'false' } }, noRoles: { matches: { properties: { // Note: types of url, search, tel, and email are listed // as not allowed roles however since they are text // types they should be allowed to have role=combobox type: [ 'color', 'date', 'datetime-local', 'file', 'month', 'number', 'password', 'range', 'reset', 'submit', 'time', 'week' ] } }, allowedRoles: false }, hidden: { matches: { properties: { type: 'hidden' } }, // Note: spec change (do not count as phrasing) contentTypes: ['flow'], allowedRoles: false, noAriaAttrs: true }, image: { matches: { properties: { type: 'image' } }, allowedRoles: [ 'link', 'menuitem', 'menuitemcheckbox', 'menuitemradio', 'radio', 'switch' ], // 5.3 input type="image" namingMethods: [ 'altText', 'valueText', 'labelText', 'titleText', 'buttonDefaultText' ] }, radio: { matches: { properties: { type: 'radio' } }, allowedRoles: ['menuitemradio'], implicitAttrs: { 'aria-checked': 'false' } }, textWithList: { matches: { properties: { type: 'text' }, attributes: { list: '/.*/' } }, allowedRoles: false }, default: { // Note: spec change (do not count as phrasing) contentTypes: ['interactive', 'flow'], allowedRoles: ['combobox', 'searchbox', 'spinbutton'], implicitAttrs: { 'aria-valuenow': '' }, // 5.1 input type="text", input type="password", input type="search", input type="tel", input type="url" // 5.7 Other Form Elements namingMethods: ['labelText', 'placeholderText'] } } }, ins: { contentTypes: ['phrasing', 'flow'], allowedRoles: true }, kbd: { contentTypes: ['phrasing', 'flow'], allowedRoles: true }, label: { contentTypes: ['interactive', 'phrasing', 'flow'], allowedRoles: false, chromiumRole: 'Label' }, legend: { allowedRoles: false }, li: { allowedRoles: [ 'menuitem', 'menuitemcheckbox', 'menuitemradio', 'option', 'none', 'presentation', 'radio', 'separator', 'tab', 'treeitem', 'doc-biblioentry', 'doc-endnote' ], implicitAttrs: { 'aria-setsize': '1', 'aria-posinset': '1' } }, link: { contentTypes: ['phrasing', 'flow'], allowedRoles: false, noAriaAttrs: true }, main: { contentTypes: ['flow'], allowedRoles: false, shadowRoot: true }, map: { contentTypes: ['phrasing', 'flow'], allowedRoles: false, noAriaAttrs: true }, math: { contentTypes: ['embedded', 'phrasing', 'flow'], allowedRoles: false }, mark: { contentTypes: ['phrasing', 'flow'], allowedRoles: true }, menu: { contentTypes: ['flow'], allowedRoles: [ 'directory', 'group', 'listbox', 'menu', 'menubar', 'none', 'presentation', 'radiogroup', 'tablist', 'toolbar', 'tree' ] }, meta: { variant: { itemprop: { matches: '[itemprop]', contentTypes: ['phrasing', 'flow'] } }, allowedRoles: false, noAriaAttrs: true }, meter: { contentTypes: ['phrasing', 'flow'], allowedRoles: false, chromiumRole: 'progressbar' }, nav: { contentTypes: ['sectioning', 'flow'], allowedRoles: ['doc-index', 'doc-pagelist', 'doc-toc'], shadowRoot: true }, noscript: { contentTypes: ['phrasing', 'flow'], allowedRoles: false, noAriaAttrs: true }, object: { variant: { usemap: { matches: '[usemap]', contentTypes: ['interactive', 'embedded', 'phrasing', 'flow'] }, default: { contentTypes: ['embedded', 'phrasing', 'flow'] } }, allowedRoles: ['application', 'document', 'img'], chromiumRole: 'PluginObject' }, ol: { contentTypes: ['flow'], allowedRoles: [ 'directory', 'group', 'listbox', 'menu', 'menubar', 'none', 'presentation', 'radiogroup', 'tablist', 'toolbar', 'tree' ] }, optgroup: { allowedRoles: false }, option: { allowedRoles: false, implicitAttrs: { 'aria-selected': 'false' } }, output: { contentTypes: ['phrasing', 'flow'], allowedRoles: true, // 5.6 output Element namingMethods: ['subtreeText'] }, p: { contentTypes: ['flow'], allowedRoles: true, shadowRoot: true }, param: { allowedRoles: false, noAriaAttrs: true }, picture: { contentTypes: ['embedded', 'phrasing', 'flow'], allowedRoles: false, noAriaAttrs: true }, pre: { contentTypes: ['flow'], allowedRoles: true }, progress: { contentTypes: ['phrasing', 'flow'], allowedRoles: true, implicitAttrs: { 'aria-valuemax': '100', 'aria-valuemin': '0', 'aria-valuenow': '0' } }, q: { contentTypes: ['phrasing', 'flow'], allowedRoles: true }, rp: { allowedRoles: true }, rt: { allowedRoles: true }, ruby: { contentTypes: ['phrasing', 'flow'], allowedRoles: true }, s: { contentTypes: ['phrasing', 'flow'], allowedRoles: true }, samp: { contentTypes: ['phrasing', 'flow'], allowedRoles: true }, script: { contentTypes: ['phrasing', 'flow'], allowedRoles: false, noAriaAttrs: true }, section: { contentTypes: ['sectioning', 'flow'], allowedRoles: [ 'alert', 'alertdialog', 'application', 'banner', 'complementary', 'contentinfo', 'dialog', 'document', 'feed', 'log', 'main', 'marquee', 'navigation', 'none', 'note', 'presentation', 'search', 'status', 'tabpanel', 'doc-abstract', 'doc-acknowledgments', 'doc-afterword', 'doc-appendix', 'doc-bibliography', 'doc-chapter', 'doc-colophon', 'doc-conclusion', 'doc-credit', 'doc-credits', 'doc-dedication', 'doc-endnotes', 'doc-epigraph', 'doc-epilogue', 'doc-errata', 'doc-example', 'doc-foreword', 'doc-glossary', 'doc-index', 'doc-introduction', 'doc-notice', 'doc-pagelist', 'doc-part', 'doc-preface', 'doc-prologue', 'doc-pullquote', 'doc-qna', 'doc-toc' ], shadowRoot: true }, select: { variant: { combobox: { matches: { attributes: { multiple: null, size: [null, '1'] } }, allowedRoles: ['menu'] }, default: { allowedRoles: false } }, contentTypes: ['interactive', 'phrasing', 'flow'], implicitAttrs: { 'aria-valuenow': '' }, // 5.7 Other form elements namingMethods: ['labelText'] }, slot: { contentTypes: ['phrasing', 'flow'], allowedRoles: false, noAriaAttrs: true }, small: { contentTypes: ['phrasing', 'flow'], allowedRoles: true }, source: { allowedRoles: false, noAriaAttrs: true }, span: { contentTypes: ['phrasing', 'flow'], allowedRoles: true, shadowRoot: true }, strong: { contentTypes: ['phrasing', 'flow'], allowedRoles: true }, style: { allowedRoles: false, noAriaAttrs: true }, svg: { contentTypes: ['embedded', 'phrasing', 'flow'], allowedRoles: ['application', 'document', 'img'], chromiumRole: 'SVGRoot', namingMethods: ['svgTitleText'] }, sub: { contentTypes: ['phrasing', 'flow'], allowedRoles: true }, summary: { allowedRoles: false, // 5.8 summary Element namingMethods: ['subtreeText'] }, sup: { contentTypes: ['phrasing', 'flow'], allowedRoles: true }, table: { contentTypes: ['flow'], allowedRoles: true, // 5.11 table Element namingMethods: ['tableCaptionText', 'tableSummaryText'] }, tbody: { allowedRoles: true }, template: { contentTypes: ['phrasing', 'flow'], allowedRoles: false, noAriaAttrs: true }, textarea: { contentTypes: ['interactive', 'phrasing', 'flow'], allowedRoles: false, implicitAttrs: { 'aria-valuenow': '', 'aria-multiline': 'true' }, // 5.1 textarea namingMethods: ['labelText', 'placeholderText'] }, tfoot: { allowedRoles: true }, thead: { allowedRoles: true }, time: { contentTypes: ['phrasing', 'flow'], allowedRoles: true }, title: { allowedRoles: false, noAriaAttrs: true }, td: { allowedRoles: true }, th: { allowedRoles: true }, tr: { allowedRoles: true }, track: { allowedRoles: false, noAriaAttrs: true }, u: { contentTypes: ['phrasing', 'flow'], allowedRoles: true }, ul: { contentTypes: ['flow'], allowedRoles: [ 'directory', 'group', 'listbox', 'menu', 'menubar', 'none', 'presentation', 'radiogroup', 'tablist', 'toolbar', 'tree' ] }, var: { contentTypes: ['phrasing', 'flow'], allowedRoles: true }, video: { variant: { controls: { matches: '[controls]', contentTypes: ['interactive', 'embedded', 'phrasing', 'flow'] }, default: { contentTypes: ['embedded', 'phrasing', 'flow'] } }, allowedRoles: ['application'], chromiumRole: 'video' }, wbr: { contentTypes: ['phrasing', 'flow'], allowedRoles: true } }; export default htmlElms;