---
title: "Roles: the WAI-ARIA data and the role computation"
url: https://recursiveai.org/onehtml/docs/accessibility-roles/
description: "RecursiveAI.OneHtml.Accessibility computes the role of every element from markup alone, before any style is known. This document is normative for that…"
version: "1.0.0"
source: https://github.com/recursive-ai-oss/onehtml-csharp/blob/cecf589eb9acfc9e1fbc529486dfef519a32c19c/docs/accessibility-roles.md
---

# Roles: the WAI-ARIA data and the role computation

`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:

1. **Explicit role** (WAI-ARIA 1.2 section 8.1). Split the `role` attribute on ASCII whitespace; the first token that
   names a non-abstract role, ASCII case-insensitively, wins (`image` counts as `img`). Tokens before it that are unknown
   or abstract are reported. `region` and `form` tokens are skipped when the element has no accessible name (profile
   entry `unnamed-landmark-token`). At most `MaxRoleTokens` (1024) tokens are examined.
2. **Implicit role** from the mapping above.
3. **Static focusability** (HTML 6.6.3 without style): a `tabindex` that parses as an integer, links with `href`,
   enabled `button`, `select`, `textarea` and non-hidden `input`, `iframe`, the first `summary` of a `details`, media with
   `controls`, editing hosts; never inside an `inert` subtree, and never for a control disabled by its own attribute or by
   a disabled `fieldset` ancestor (outside that fieldset's first `legend`).
4. **Resolution.**
   - explicit non-presentational role: that role (`Explicit`);
   - explicit `none` or `presentation`: if the element is focusable or has any global `aria-*` attribute, the implicit
     role (`ConflictResolved`); otherwise `none` (`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: `table` owns `row`, `rowgroup`, `cell`, `columnheader`, `gridcell`,
     `rowheader`; `list` owns `listitem`) 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`).
5. **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.
