WAI-ARIA TreeView — release blocker
OrgSelector implements the WAI-ARIA Authoring Practices TreeView pattern. All trees expose the standard tree semantics regardless of variant.
Roles & properties
- The tree root has
role="tree". In multi / cascade / include-descendants modes it also carriesaria-multiselectable="true". - Each branch is a
role="treeitem"with:aria-expanded— present and set on rows that can expand.aria-selected— reflects current selection in single / multi modes.aria-checked(true/false/'mixed') — used in cascade mode to express partial subtrees.aria-level,aria-setsize,aria-posinset— ancestry / position information for screen readers.aria-disabled="true"for non-pickable or explicitly disabled rows.
- Groups of children render as
role="group"so the relationship is unambiguous.
Keyboard
The full tree keyboard model is wired up:
| Key | Action |
|---|---|
↑ / ↓ | Move focus to the previous / next visible row. |
← | Collapse an expanded row, or focus its parent. |
→ | Expand a collapsed row, or focus its first child. |
Home / End | Jump to the first / last visible row. |
Enter / Space | Toggle selection (or expand for non-pickable parents). |
a–z, 0–9 | Typeahead — focus the next row whose label starts with the typed buffer. The buffer resets after 700 ms. |
* | Expand every sibling of the focused row. |
Tab | Leaves the tree to the next focusable control. Tree manages its own roving focus. |
In command variant the search input behaves like a Command palette: typing filters; ↑ / ↓ move through results; Enter selects.
Focus management
- Each tree has a single tabstop (roving
tabindex). Focus enters where the user last left it (or the first selected row, or the first row). - Opening the popover/drawer auto-focuses the search input when
searchableis on, otherwise the first row. - Closing the popover returns focus to the trigger.
Form field semantics
labelrenders a real<label>linked to the trigger viaid/htmlFor.helpTextis wired up viaaria-describedby.errorTexttogglesaria-invalid="true"and is associated viaaria-describedbyalongside help text.requiredadds an asterisk andaria-required="true".
Cascade mode signals
- Parents with a partial subtree render a checkbox in
'mixed'(indeterminate) state — botharia-checked="mixed"and a visual middle-bar fill. Colour is never the only signal. - Selecting an indeterminate parent selects every descendant; deselecting it clears the subtree.
Async loading
- The expand chevron renders a spinner during
loadChildrencalls. The row setsaria-busy="true"while loading. - If
loadChildrenrejects, a retry affordance appears withrole="button"and an action-describingaria-label.
Reduced motion / contrast
- Expand/collapse uses CSS transitions ≤ 200 ms and respects
prefers-reduced-motion. - Selected rows use a fill plus checkmark — colour is reinforcement, not the only signal.
- Focus rings meet WCAG 2.1 AA 3:1 non-text contrast.
Audit checklist
- Tree root has
role="tree". - Each row has
role="treeitem"+ correctaria-level,aria-setsize,aria-posinset. - Cascade mode reports
aria-checked="mixed"for partial subtrees. -
←/→collapse/expand and move focus per the APG spec. -
Tabdoes NOT cycle through every row — it leaves the tree. - Typeahead resets after 700 ms.
- Trigger has an accessible name even when no value is selected.
- Error text is exposed via
aria-describedbyand togglesaria-invalid. - 4.5:1 contrast on labels in selected, indeterminate, and disabled states.