A trace tree you can navigate
A collapsible trace of an agent run is a tree, so the APG has already specified its keyboard model — and treating it as a pile of nested disclosures is the shortcut that makes a fifty-step run unusable without a mouse.
The run finished. The trace panel shows what it did: a plan step, then eleven document steps, each containing a fetch, an extraction, a confidence evaluation, and sometimes a retry with its own children. It is built out of nested <details> elements, which was the fastest correct-looking thing to build and which nobody has objected to, because with a mouse it is fine. You click the triangle you want.
Put the mouse down and it stops being fine, and the reason is not a bug. It is that a disclosure is a pattern for one thing that opens, and a trace is a different shape entirely. The APG already specified the right shape, keystroke by keystroke, years before anyone built an agent.
What a disclosure can and cannot express
The APG disclosure pattern is the smallest thing in the guide: a trigger with role="button", aria-expanded reflecting whether the controlled content is visible, aria-controls optionally pointing at it, and Enter and Space both toggling. That is the entire pattern.
Notice everything it has no vocabulary for. There is no notion of a parent or a child, so a nested disclosure is a disclosure that happens to contain another one and nothing in the accessibility tree says they are related. There is no level, so nothing announces how deep you are. There is no set size or position, so nothing announces “3 of 11.” And there is no keyboard model between siblings: the pattern defines what a single trigger does when you activate it, and says nothing about moving from one to the next, because from the disclosure’s point of view there is no next.
The practical consequence follows from the pattern definition rather than from any measurement. Every disclosure trigger is a button, and every button is a tab stop, so a nested-disclosure trace costs one Tab press per visible node. Expand a few branches of a long run and the distance to the last node is the count of everything before it. Collapse them to shorten the walk and you have hidden the thing you were looking for. There is no third option, because the pattern has no navigation model to offer one.
The tree pattern, and what part of it is normative
This distinction is the one to get right, because conflating it is the most common mistake in the subject and it is how a good finding gets dismissed.
- The roles and properties are ARIA’s.
tree,treeitem,group,aria-expanded,aria-selected,aria-level,aria-setsize,aria-posinsetare defined in the WAI-ARIA specification, which is normative. Using them wrongly is a real conformance problem, reachable through 4.1.2 Name, Role, Value at Level A. - The keyboard model is the APG’s. The Authoring Practices Guide is guidance about how to apply that specification. It is not normative, and no criterion says “implement the APG tree keyboard model.” What WCAG obliges is 2.1.1 Keyboard at Level A — all functionality operable through a keyboard interface. The APG gives you a well-tested way to satisfy that, not the only permitted one.
So the finding you write is “this fails 2.1.1 at Level A, and the APG tree pattern is the established way to fix it,” never “this violates the APG.” Nothing violates the APG. Say it the second way once in front of an auditor and everything else you claim gets discounted.
The structure itself, from the pattern: all tree nodes are contained in or owned by an element with role tree; each node has role treeitem; a parent node contains or owns an element with role group holding its children. aria-expanded carries open or closed state for parents, aria-selected or aria-checked carries selection, and where the full node set is not in the DOM — which for a streamed trace it usually is not — each node carries aria-level, aria-setsize and aria-posinset so a screen reader can still say where you are.
That last clause is the one that pays for the rewrite. A tree announces “retry, level 3, 2 of 4, collapsed.” Nested disclosures announce “retry, button, collapsed,” and the user has to reconstruct the shape of the run from the indentation they cannot see.
Roving tabindex or aria-activedescendant
A tree is a composite widget, and Developing a Keyboard Interface states the rule that makes it usable: the page tab sequence includes only one focusable element of a composite. One Tab press to get into the trace, one to get out, and arrow keys to move inside. That is the entire difference between a trace you can skip past and a trace you are trapped walking through.
There are two ways to manage focus inside, and the tree pattern is specific about which is its baseline. It says the tree role “supports the aria-activedescendant property, which provides an alternative to moving DOM focus among treeitem elements”. Read the framing carefully: moving DOM focus — a roving tabindex — is the implicit default, and aria-activedescendant is offered as the alternative. That is the reverse of the combobox pattern, where DOM focus has to stay pinned on the text input and aria-activedescendant is therefore the only option.
The keyboard-interface page gives one concrete reason to prefer the roving tabindex where you have the choice: with real DOM focus the user agent scrolls the newly focused element into view for you. In a trace that can run to hundreds of nodes in a scrolling panel, that is not a detail. Implement aria-activedescendant and scrolling the active node into view becomes your code, and it is code that is usually missing.
Check your recall
Answer from memory — no scrolling back.
Retrieval check
Sketch the accessibility contract for the trace panel: the roles, the properties that carry position, the tab-stop count, and the criterion you would cite if it is wrong.
Check your answer
- Roles. A container with
role="tree", every step asrole="treeitem", and each parent’s children wrapped inrole="group". - State.
aria-expandedon every parent node, true or false. A leaf node does not get one — the absence is what marks it as a leaf. - Position.
aria-level,aria-setsizeandaria-posinseton each node, because a streamed trace does not have every node in the DOM and the screen reader cannot count what is not there. - Tab stops: one. The whole composite takes a single stop in the page tab sequence. Arrows move inside it, and a roving
tabindexis the default way to do that, with the user agent handling scroll-into-view. - The citation. 2.1.1 Keyboard at Level A for functionality that cannot be operated from the keyboard, and 4.1.2 Name, Role, Value at Level A where the roles and states are wrong or missing. The APG is the remedy, never the violation.
Hands on
Re-shape the trace, and record it as two findings
Done when: CONFORMANCE.md carries separate rows for 2.1.1 Keyboard at Level A and 4.1.2 Name, Role, Value at Level A on the trace panel, each with a runnable test, a remediation cost and a human or unchecked provenance — and the remediation column names the APG tree pattern as guidance rather than as an obligation.
- Open the trace for a real run with several levels of nesting, expand the branches you would normally expand, and count the Tab presses to reach the last node. Write the number down. It is the most persuasive line in the finding and it takes ninety seconds to obtain.
- With a screen reader on a nested node, note exactly what is announced. You are listening for whether depth and position are conveyed at all. “Button, collapsed” and “treeitem, level 3, 2 of 4, collapsed” are the two outcomes, and the gap between them is the finding.
- Add the 2.1.1 Keyboard row at Level A using the Tab count as the test. Add a separate 4.1.2 Name, Role, Value row at Level A for the missing structural semantics. Two criteria, two fixes, two costs — do not merge them.
- In the remediation column, name the APG tree pattern as the route and say in the same breath that it is non-normative guidance. This is the row where a reviewer will test whether you know the difference.
- Decide roving
tabindexoraria-activedescendantand write down which, with the reason. If you choosearia-activedescendant, add a line noting that scrolling the active node into view is now your code, so the remediation cost is higher than it looks. - Bring both rows into the chat. I will check that they are separate, that the remediation names the APG as guidance rather than as a requirement, and that the test column contains something another engineer could run without asking you what you meant.
What this does not cover
This lesson assumes the trace exists as a finished structure. It does not deal with what a screen reader hears while the trace is still growing, node by node, during the run — which is the streaming problem again in a different costume, and which the announcing-the-stream lesson answers the same way: the growing structure is not a live region, and one polite transitions region carries the state.
Nor does it settle the tree implementation itself. The full keyboard model — what each arrow does at a collapsed node versus an expanded one, type-ahead, Home and End, the asterisk convention — is specified in the APG pattern linked above, and building it is the four-patterns lesson’s job rather than this one’s.
What remains is the document. You have rows for streaming, cancel, interrupt and trace, several resting on reasoning that no source supports, and a note is only worth what the person reading it thinks it is. The conformance-note lesson is about signing it.
Read this next — primary source
APG pattern: Tree ViewW3C Web Accessibility Initiative — free. Guidance about applying a normative specification, not the specification itself.
This lesson takes the role structure and the two focus-management options from it. Read the whole pattern for the keyboard model, which is the part you cannot derive and the part a trace viewer needs most: what the arrow keys do at a collapsed node versus an expanded one, the type-ahead behaviour, and the asterisk-expands-all-siblings convention. The linked examples are working implementations you can read the source of, which is faster than reading the prose twice.
Stuck, curious, or think this lesson is wrong? Ask your teaching agent. The lessons are the scaffold; the conversation is where the learning gets unstuck.