Skip to content

disclosure

disclosure declares that an element is the trigger for a region that expands and collapses — an accordion header, a “show more” control. Without it the header cannot be focused or activated from the keyboard, and nothing connects it to the region it controls, so assistive technology cannot report that the control is expandable, what state it is in, or where the content went.

Status — React: Implemented • Web Components: Implemented • iOS: Not yet planned • Android: Not yet planned

Roles

Apply the following roles to elements:

RoleTypeElement
disclosureControlElement of type: container
labelPartElement of type: text or nested instance prop of type: string
panelPartElement of type: container or type: slot
indicatorPartElement of type: container or type: glyph

There is no disclosure ARIA role. The emission is a button plus two attributes; role="disclosure" is never emitted because it is not a valid ARIA value.

The trigger and the panel are one concept, not two. The trigger carries the role, the region carries panel, and aria-controls follows from the pairing. The panel is usually a sibling of the trigger rather than a descendant, so parts resolve by proximity.

It emits semantics and linkage only. Visibility stays with CSS and the variant conditions the analysis already produced, so a library that animates the transition keeps working.

States

The disclosure role is typically applied in conjunction with the following states:

StateEffectClassify?
expandedaria-expanded="true", flipped by the wired handlerRecommended
collapsedaria-expanded="false"Recommended
disabledNatively disabled, enforced by the platformRecommended
hover / activeNative, on the trigger itselfRecommended, if the library styles it
focus / focus-visibleNative focus indicatorOptional — prefer the platform default

Read more about states in specs.

Specs

Component anatomy typically has elements and roles like:

anatomy:
header:
type: container
role: disclosure
chevron:
type: glyph
role: indicator
panel:
type: container
role: panel

Figma

Annotate the following layers:

  • header as role:disclosure — on the trigger layer
  • panel as role:panel on the region it controls, wherever it sits
  • chevron as role:indicator

The panel does not need to be a descendant of the trigger. Siblings under a shared root are the usual shape and resolve correctly. Two disclosures in one component need unambiguous panels, or the transform errors.

React

Implementation

<Accordion
label="Baggage allowance"
expanded={open}
onExpandedChange={setOpen}
>
<p>One carry-on bag and one personal item.</p>
</Accordion>

Before / After

// before
<div className="accordion-header" data-element="header" data-expanded={p.expanded}>
{/* … */}
</div>
<div className="accordion-panel" data-element="panel">{p.children}</div>
// after
<button
type="button"
className="accordion-header"
data-element="header"
aria-expanded={isExpanded}
aria-controls={panelId}
onClick={() => { setExpanded(!isExpanded); p.onExpandedChange?.(!isExpanded); }}
>
{/* … */}
</button>
<div className="accordion-panel" data-element="panel" id={panelId}>{p.children}</div>

Two elements change and neither changes depth.

Contract

PropTypeTierGenerated body
onExpandedChange?(expanded: boolean) => voidMUSTWired — flips expanded, then calls the prop
onClick?(e: MouseEvent) => voidSHOULDStub, called after the toggle
expandedboolean—The existing variant prop

Web Components

As button for the trigger. One difference that matters: aria-controls is an id reference and does not cross a shadow boundary, so a trigger and panel in different components cannot be linked. Both must live in the same component’s shadow root — which is the usual shape for an accordion, but rules out a disclosure whose panel arrives as slotted content from elsewhere.

iOS

Not yet planned. Intended binding:

TypeDisclosureGroup
AnnouncedThe expanded state; double-tap toggles
LinkageStructural — the panel is the group’s content, so no id reference exists

Android

Not yet planned. Intended binding:

TypeToggleable header + AnimatedVisibility
SemanticsModifier.semantics { expand() / collapse() }
Announced”Expanded” / “collapsed”; double-tap toggles

Additional details

Degradation

SituationResult
No panel partaria-expanded only, plus a warning
Panel absent in the current variantaria-controls becomes conditional, matching the panel’s render condition
Two disclosures, ambiguous panelError naming both candidates — never a silent pick
Separate collapsed / expanded label propsWarns. Only one binds to the anatomy, so an expanded trigger would announce its collapsed label

The last one is fixed by a convention in conventions/specs.yaml pairing the alternate text prop with the state that selects it. The accessibility block is shaped as objects precisely so it can grow that field.

Wired state

onExpandedChange follows the wired state model: internal state seeded from the expanded prop, flipped on activation, then the consumer callback. The accordion opens and closes before a consumer attaches anything.

Prerequisite: an expanded classification in the states convention naming the prop. Without it the handler degrades to a stub and warns.

See also

  • panel — the controlled region
  • button — the same base emission without expansion
  • Roles overview — the vocabulary and how roles are authored