RecursiveAI.OneHtml.Accessibility computes the role of every element from markup alone, before any style is known.
This document is normative for that part: the data it is built from, the algorithm, and what it reports. Choices the
specifications leave open are in accessibility-chromium-profile.md.
Data
The taxonomy is generated by tools/aria/generate-aria.py from specification documents pinned in
tools/aria/pins.json (URL, title, SHA-256 of the document as retrieved on 2026-10-25). tools/aria/fetch-specs.py CACHE
downloads them and checks the hashes; generate-aria.py CACHE rewrites the JSON under
src/OneHtml.Accessibility/Aria/Data/ byte for byte reproducibly. The files are embedded in the assembly and read with
System.Text.Json; each has a version field (currently 1).
| Source | Used for |
|---|---|
| WAI-ARIA 1.2, W3C Recommendation, 6 June 2023 | roles, states and properties |
| Digital Publishing WAI-ARIA Module 1.1, W3C Recommendation, 12 June 2025 | the doc-* roles |
| WAI-ARIA Graphics Module 1.0, W3C Recommendation | the graphics-* roles |
| HTML Accessibility API Mappings 1.0, W3C Working Draft, 21 October 2026 | element and attribute mapping tables (text extract) |
| WAI-ARIA 1.3, W3C Editor's Draft, 21 October 2026 | only what browsers already ship and the 1.2 text lacks (below) |
Draft additions. Three roles, comment, mark and suggestion, and five properties, aria-braillelabel,
aria-brailleroledescription, aria-description, aria-colindextext and aria-rowindextext, come from the 1.3 draft
(module Aria13Draft). mark is needed by HTML-AAM's mark element. The draft properties are added to the supported
lists of the roles they apply to (the first three to every role). image is not a role of its own: it is treated as
the preferred synonym of img, as the draft and Core-AAM's computed role do. The draft's sectionheader and
sectionfooter roles are not included (see the profile entry section-header-footer).
Counts
| Roles | 141: WAI-ARIA 1.2 94 (12 abstract), Digital Publishing 41, Graphics 3, 1.3 draft 3 |
| States and properties | 53: 10 states and 43 properties; 20 are global |
| HTML-AAM element entries | 147 |
| HTML-AAM attribute entries | 162 |
Role record
For every role: name, module, abstract, superclasses, requiredContext, requiredOwned (each entry a chain such
as ["group", "option"]; any chain satisfies the requirement), required, requiredWhenFocusable (separator's
aria-valuenow), supported (own, required and inherited, flattened), deprecated (the supported ones the specification
marks deprecated on that role), prohibited, nameFrom (author, contents, prohibited), nameRequired,
childrenPresentational and implicit (default values, such as aria-live polite for log). none shares the
characteristics of presentation; the data of presentation is used for both.
One entry is added to the generated data by tools/aria/generate-aria.py: list owns listitem directly or through a
group (["group", "listitem"]). The WAI-ARIA 1.2 role table names only listitem; WAI-ARIA 1.1 listed the group chain
and ACT rule bc4a75 still passes that shape, so the chain is kept.
Property record
For every state and property: name, module, isState, type (boolean, booleanUndefined, tristate, idref,
idrefList, integer, number, string, token, tokenList), tokens, default, global, globalExceptRoles
(the roles a global attribute is prohibited on), usedInRoles, inheritsIntoRoles and deprecated (the whole attribute).
HTML-AAM mapping
HtmlRoleMapper implements section 3.5 (element role mappings), with every condition the tables state. The text of the
tables (heading, WAI-ARIA mapping column, comments) is kept in html-aam-elements.json, and the attribute tables of
section 3.6 in html-aam-attributes.json; a test compares every entry that is a plain tag name with the mapper.
| Element | Role |
|---|---|
a, area | link with href, otherwise generic |
img | img (computed image); with an empty alt after trimming ASCII whitespace, none unless aria-label has text or aria-labelledby names an element |
header, footer | banner, contentinfo when no article, aside, main, nav or section ancestor exists; otherwise generic |
section, form | region, form when the element has an accessible name; otherwise generic |
aside | complementary when the nearest ancestor among article, aside, nav, section, main, body is main or body; otherwise complementary only with a name, else generic |
select | listbox with multiple or size above 1, otherwise combobox |
input | by type: button, image, reset, submit: button; checkbox: checkbox (switch with the switch attribute); radio; number: spinbutton; range: slider; search: searchbox; text, tel, url, email: textbox; text, search, tel, url and email become combobox when list names a datalist of the document; color, date, datetime-local, file, hidden, month, password, time, week: no role. An invalid type is text. |
th | columnheader for scope col or colgroup, rowheader for row or rowgroup; with scope auto, columnheader in a thead or a row without td, rowheader in a row with one |
td | gridcell when the nearest table ancestor has an explicit grid or treegrid role, otherwise cell |
li | listitem when the nearest ancestor whose role is not generic or presentational has the list role; otherwise generic |
option | option in a select, an optgroup of a select, or a datalist; otherwise no role |
summary | none for the first summary child of a details, otherwise generic |
| others | as the table lists; elements it does not list are generic; "No corresponding role" is no role |
"Has an accessible name" is not computed here. RoleOptions.HasAccessibleName supplies the answer; without it the role
without a name is reported and NodeRole.RoleWhenNamed holds the role the name would give.
HtmlImplicitProperties.Get(element) returns the implied states and properties of section 3.6 that markup alone
determines (required → aria-required, disabled, readonly, checked, selected, multiple, placeholder,
list → aria-controls, min/max/value of meter, progress, range and number inputs, colspan, rowspan,
open of details on its summary, the heading level, textarea multiline), each with the id of the table entry.
Role computation
RoleComputer.Compute(document, options, cancellationToken) visits every element in document order with an explicit
stack. It does not enter template content. For each element:
- Explicit role (WAI-ARIA 1.2 section 8.1). Split the
roleattribute on ASCII whitespace; the first token that names a non-abstract role, ASCII case-insensitively, wins (imagecounts asimg). Tokens before it that are unknown or abstract are reported.regionandformtokens are skipped when the element has no accessible name (profile entryunnamed-landmark-token). At mostMaxRoleTokens(1024) tokens are examined. - Implicit role from the mapping above.
- Static focusability (HTML 6.6.3 without style): a
tabindexthat parses as an integer, links withhref, enabledbutton,select,textareaand non-hiddeninput,iframe, the firstsummaryof adetails, media withcontrols, editing hosts; never inside aninertsubtree, and never for a control disabled by its own attribute or by a disabledfieldsetancestor (outside that fieldset's firstlegend). - Resolution.
- explicit non-presentational role: that role (
Explicit); - explicit
noneorpresentation: if the element is focusable or has any globalaria-*attribute, the implicit role (ConflictResolved); otherwisenone(Explicit). Non-global attributes do not prevent it; - no explicit role, the element is a required owned element of a presentational parent (the closure of the parent's
native role's required owned elements:
tableownsrow,rowgroup,cell,columnheader,gridcell,rowheader;listownslistitem) and is not focusable:none(InheritedPresentational); an element with its own non-presentational explicit role keeps it; - otherwise the implicit role (
Implicit), or no role (None).
- explicit non-presentational role: that role (
- Presentational children. A descendant of an element whose resolved role has presentational children is
none(ChildrenPresentational) unless focusable. The element's attribute checks still use its own role.
The result for each element is a NodeRole: Role, ComputedRole (Core-AAM's names: presentation is none, img is
image, directory is list; empty when there is no role), ExplicitRole, ImplicitRole, RoleWhenNamed, Origin,
IsFocusable, IsDisabled, IsInert and Diagnostics.
Diagnostics
For the element's own role (an inherited presentational role is not used for these checks):
| Kind | When |
|---|---|
InvalidRoleToken, AbstractRole | a token before the chosen one (or every token when none is usable) is unknown or abstract |
ProhibitedAttribute | the role prohibits the attribute, or the attribute is global with the role in its exception list |
UnsupportedAttribute | a non-global attribute the role does not support |
DeprecatedAttribute | deprecated on the role, or the whole attribute is |
UnknownAttribute | an aria- name that is not a state or property |
InvalidAttributeValue | the value does not fit the type: booleans, tristates and tokens against the token list, token lists token by token, integers (with the lower bounds in the profile), real numbers, an ID reference with more than one token |
BrokenIdReference | an ID reference names no element of the document (each token of a list) |
MissingRequiredAttribute | an explicit role that differs from the element's native role lacks a required state or property that the element does not supply natively (profile entry required-attribute-native); requiredWhenFocusable applies to focusable elements |
MissingRequiredContext, MissingRequiredOwnedElements | reserved: they depend on the tree with aria-owns re-parenting, which a later step builds. AriaRole.RequiredContext and AriaRole.RequiredOwned hold the data for it. |
LimitExceeded | a limit stopped the scan of role tokens, ID references or diagnostics |
An empty attribute value is treated as not specified and is not reported. Diagnostics are in attribute order.
Limits
No recursion follows the depth of the input. Everything is linear in the number of elements, attributes and tokens:
MaxRoleTokens (1024), MaxIdReferencesPerAttribute (4096) and MaxDiagnosticsPerElement (64) bound the work and the
output per element; the per-row header scan runs once per row. The computation observes the cancellation token every 1024
nodes.
Conformance
testdata/wpt/aria/ holds the role pages of web-platform-tests html-aam/ and wai-aria/role/ at the commit
recorded in MANIFEST.md. WptRoleTests translates the AriaUtils.verify* and assignAndVerifyRolesByRoleNames calls
and the get_computed_role assertions of those pages, with a name probe of aria-label, a resolvable
aria-labelledby, or title. testdata/wpt/aria/expectations.tsv lists each known failure with its reason.
| Result | Count |
|---|---|
| Pass | 536 |
| Known failure (tentative pages) | 18 |
| Not applicable | 5 |
Known failures are all on tentative pages: sectionheader/sectionfooter for header and footer inside sectioning
content or main (the profile exposes generic), the draft "minimum role" rule that gives div elements with
autofocus, draggable or popover the group role, and a li that only aria-owns makes a list item. The five
not-applicable cases are expectations the pages themselves mark ambiguous (SPEC_AMBIGUOUS_LOG_VALUE). Cases a page
builds with script (document.createElement) are not run. The pages html-aam/names.html, hidden-names.html,
figure-name-no-figcaption.tentative.html, img-src-srcset-roles.tentative.html and accname-computation-by-element/ test
names or need layout and are not included.