Accessibility · Semantics and the A11y Tree

Required Owned Elements and Structural Constraints

Which roles require children, which require a parent, how engines repair an orphaned widget, and why the repair changes reading order.

ARIA roles are not independent labels. Each role’s definition states which roles must be its children and which role must be its parent, and those two constraints form a grammar for the tree. A listbox requires option children; an option requires a listbox parent, or a group whose parent is a listbox. A tab requires a tablist parent. A row requires a table, grid, treegrid, or rowgroup parent, and a rowgroup requires one of those three. The constraints are normative, and a tree that violates them is not merely untidy: consumers implement behaviour that assumes they hold.

The constraints exist because a role is a promise about interaction, and interaction is implemented by the consumer, not by the page. A listbox consumer implements Up and Down to move among options and needs to know where the options are. A treeitem consumer implements Right to expand and Left to collapse and needs a group child to expand. When the structure is absent, the consumer’s implementation has nothing to operate on.

Two constraints, different failure modes

aria-required-children is the outward constraint: this role must own at least one element of that role. It is checked over the subtree, not over immediate children, because wrappers between the owner and the owned element are allowed and often ignored. A listbox containing a div containing option elements satisfies the constraint, since the div is generic and ignored and its children are re-parented into the listbox in the tree.

aria-required-parent is the inward constraint: this role must be owned by an element of that role. It is checked against the nearest ancestor in the accessibility tree, which is the tree after ignored nodes are spliced and after aria-owns is applied. An option inside a div inside a listbox has the listbox as its tree parent and satisfies the constraint; an option inside a div inside a form does not.

The two constraints interact with the ignoring rules in a way that catches authors out. A li is a list item and requires a list parent. If the ul is given role="presentation" to remove its bullets, the list role disappears, the li elements are re-parented into whatever enclosed the ul, and they now violate the parent constraint. Engines respond by either exposing the li as generic or by refusing the presentational role on the ul. Which one happens depends on the engine and on whether the ul also has other attributes.

Repair strategies

When a constraint is violated, the specification says the author has made an error; it does not say what the implementation must do. Engines have converged on three strategies, and a given engine may use different ones for different roles.

The first is to honour the requested role and expose the violation. The orphan option is exposed as an option, the consumer receives it without a listbox, and the consumer’s own code decides what to do. Some consumers ignore orphaned options entirely, which produces an object that is in the tree and unreachable.

The second is to expose the orphan with a neutral role. The option becomes generic, keeps its text, and is announced as plain content. This is the safer failure: the user hears the text, does not hear a listbox, and is not offered arrow-key navigation that would not work.

The third is to synthesise the missing context. The engine inserts an implied container object of the required role around the orphan, so the tree contains a listbox whose only child is the option, even though the DOM has no such element. This preserves the consumer’s ability to navigate and produces a tree that cannot be mapped back to the markup. The synthesised object has no name, no attributes, and no geometry of its own; it is a structural placeholder.

The third strategy is the one that changes reading order, because the implied container becomes a stop in the tree traversal. A screen reader announces “list, one item” for content the author intended as loose paragraphs, and the announcement cannot be traced to anything in the source. It also interacts badly with nesting: two adjacent orphans can each get their own implied container, producing two one-item lists where the author expected one.

<div role="listbox" aria-label="Size">
  <div role="option" aria-selected="false">Small</div>
  <div role="option" aria-selected="false">Medium</div>
  <div role="option" aria-selected="true">Large</div>
</div>
<!-- satisfies required children; each option is owned by the listbox
     because the intermediate elements are absent and the options are
     direct children in both DOM and tree -->

The context requirement is stricter than the child requirement

aria-required-parent fails more often in practice because it is easier to break by moving markup. A tab is valid only inside a tablist; a menuitem only inside a menu or menubar; a treeitem only inside a tree or a group that is itself inside a tree. These widgets are usually built by a component that renders its container, so the constraint holds until the component is used inside a layout that wraps its children.

A wrapper that is not ignored is the usual culprit. A div with role="group" around the tabs, added to provide a label for a section, breaks the parent constraint, because the tabs’ tree parent is now the group, not the tablist. The fix is either to move the group outside the tablist or to accept that the group is the parent and give it the role the tabs require, which is not possible, since a group cannot be a tablist. The structural grammar has no way to express “a labelled tablist” other than naming the tablist itself.

aria-owns is the escape hatch. An element can own children that are not its DOM descendants, so a tablist can own tabs that live elsewhere in the document, restoring the parent relation without moving markup. The cost is the divergence between tree order and DOM order described in Focus Order and the Accessibility Tree, and the risk of stale handles after mutation described in Accessibility Tree Invalidation and Event Coalescing.

Where the grammar is under-specified

Several roles have constraints that are stated as recommendations rather than requirements, and the difference is not always clear from the definition text. A grid should contain row elements, but a grid whose rows are rowgroup wrappers is also valid, and a grid with a single row containing all cells is valid but defeats the point of the role. A radiogroup requires radio children, and the group is what gives the set a shared name, but the individual radio is the thing that carries the checked state and the label.

combobox is the most contested. Its popup is a container of the listbox family, but the relationship may be expressed through aria-controls rather than through ownership, and a combobox whose popup is a DOM sibling is the common implementation. The constraint therefore holds in a form that the structural check cannot verify, and engines differ on whether they expose the popup as a child of the combobox. The resulting tree shape depends on the engine, and a consumer’s arrow-key handling depends on the shape.

The second under-specified area is nesting depth. A treeitem may contain a group of further treeitems, and the group is optional when the item is collapsed. The tree structure therefore changes with expansion state, which means the tree is not a static grammar but a grammar plus a state machine. An engine that computes the tree once per frame handles this naturally; a consumer that cached the collapsed structure must re-read on the expanded event, and the event type for that change is a children-changed event rather than a state-changed event, because the children appeared.

Validation and its blind spots

Automated checks can detect a missing required child or a missing required parent by examining the computed tree, which is what makes the constraint useful for tooling: the check operates on the same structure the consumer sees, not on the markup. The blind spots are the cases where the computed tree differs from the author’s model.

An aria-owns relation that points at a nonexistent ID is silently dropped, and the owner then violates its required-children constraint even though the markup names a child. An ID that appears twice is resolved to the first element in document order, so the second owner’s relation points at the wrong object. A role applied to an element that is ignored for another reason (display: contents, or a missing box) never enters the tree, so its children are re-parented and the constraint is evaluated against a structure the author did not intend.

The checks that matter are therefore the ones that read the computed tree and report the parent-child relation as found, including the implied containers and the re-parented subtrees. A check against the DOM will report a tablist containing tab elements when the tree contains a tablist containing a group containing tab elements, and the two answers differ on exactly the cases that break consumers. The remaining question, what a role’s children are allowed to be when the role is itself a container of containers, is a property of each role’s definition rather than of the tree, and it is the subject of Container Roles and Nested Structure.

References

  1. WAI-ARIA 1.2 — Required Owned Elements — W3C
  2. WAI-ARIA 1.2 — Required Context Role — W3C
  3. ARIA in HTML — W3C
  4. WAI-ARIA Authoring Practices Guide — Patterns — W3C