panel
panel marks the region a disclosure trigger expands and collapses. aria-expanded tells assistive technology that something expands; it does not say what. Without the linkage a screen reader user hears that a control is expanded and must hunt for the content in reading order.
Status — React: Implemented • Web Components: Implemented • iOS: Not yet planned • Android: Not yet planned
This is the only way to supply that half of the pattern, because ids are generated at render and cannot be authored.
Roles
Apply the following roles to elements:
| Role | Type | Element |
|---|---|---|
panel | Part | Element of type: container or type: slot |
| Where | What is emitted |
|---|---|
| The panel element | A generated id |
The disclosure trigger | aria-controls={panelId} |
The panel’s tag, classes, and children are unchanged. The role adds linkage, never show/hide logic.
Specs
Component anatomy typically has elements and roles like:
anatomy: header: type: container role: disclosure panel: type: container role: panelFigma
Annotate the following layers:
panelasrole:panel— on the region the trigger controls
It does not need to be a descendant of the trigger.
React
Implementation
Nothing. panel is wiring the component emits for itself; a consumer never addresses it.
Before / After
// before<div className="accordion-panel" data-element="panel">{p.children}</div>
// after<div className="accordion-panel" data-element="panel" id={panelId}>{p.children}</div>The panel’s own diff is one attribute. The change that matters happens on the trigger, which gains aria-controls={panelId}.
Contract
None. panel adds an id to its own element and an attribute to a different one. The expansion event belongs to the trigger, which owns onExpandedChange.
The only parts carrying handlers are increment and decrement.
Web Components
Same emission. The id and the trigger’s aria-controls must be in the same shadow root — an id reference does not cross the boundary, so a panel arriving as slotted content from another component cannot be linked.
iOS
Not yet planned. Intended binding:
| Type | The disclosure’s content |
| Linkage | Structural — no id reference exists or is needed |
Android
Not yet planned. Intended binding:
| Type | The AnimatedVisibility content of the toggleable header |
| Linkage | Structural |
Additional details
Resolution
disclosure is not value-bearing, so panel resolves by proximity:
- The candidate disclosure whose subtree contains the panel, if there is one
- Otherwise the candidate that is the panel’s closest sibling-path ancestor’s child
| Situation | Result |
|---|---|
| Panel is a sibling of its trigger | Resolves — the usual shape, and what the proximity rule exists for |
| Two disclosures, ambiguous panel | Error naming the panel and every candidate |
No disclosure in the component | Valid. Emits the id, no wiring — the component provides a panel, and wiring arrives on composition |
See also
- disclosure — the trigger this part wires to
- errormessage — the same id-plus-attribute shape, for controls
- Roles overview — the vocabulary and how roles are authored