Designer view
Disclosure
A single collapsible region with one toggle — a button that expands or collapses an associated content panel. The atomic unit of disclosure: Accordion is a list of disclosures grouped with shared keyboard navigation, but a standalone Disclosure has no peers and no group semantics. Used for "show more" patterns in prose, expandable details rows, optional advanced settings, inline help expansion, and any single named region whose content is disclosable on demand.
When to use
Use
For a single collapsible region — "show more" patterns in prose, expandable details rows, optional advanced settings, inline help expansion, expandable filter sections. The user toggles the disclosure to reveal or hide content that is not always needed.
Avoid
For a list of disclosures grouped with shared keyboard navigation — that is `Accordion`. For mutually-exclusive parallel views — that is `Tabs`. For navigation between independent pages — that is `SidebarNav`. For blocking content reveals — that is `Modal` or `Drawer`.
Versus related
- accordion
`Accordion` is a list of disclosures grouped as a unit with shared keyboard navigation (ArrowDown / Up between items, Home / End to first / last). `Disclosure` is a single collapsible region with no peers and no group semantics. Accordion-of-one-item is anti-pattern; use Disclosure when there's only one region.
- tabs
`Tabs` always shows one panel at a time, replaces on selection, and has visible peer labels for unselected panels. `Disclosure` shows zero or one panel and has no peers. Tabs are lateral; Disclosure is hierarchical (the trigger is a heading or sentence-fragment in the document outline).
- modal
`Modal` blocks the page and demands explicit response; `Disclosure` is non-blocking and reveals inline. Modal severs spatial relationship to the underlying content; Disclosure preserves it.
Figma anatomy
| Slot | Figma type | Hint |
|---|---|---|
trigger | instance | Button instance; visual treatment varies by variant (inline link-style, standalone block) |
icon | instance | Icon component instance; rotation or swap bound to expanded state |
panel | frame | Auto-layout vertical frame; visibility bound to expanded state |
Token usage per slot
trigger- spacing
- padding
spacing.compact - gap
spacing.compact
- padding
- color
- foreground
color.text.accent - ring
color.border.focus
- foreground
- typography
- size
text.md - weight
weight.medium
- size
icon- color
- foreground
color.text.muted
- foreground
panel- spacing
- padding
spacing.compact
- padding
- color
- foreground
color.text.primary
- foreground
- typography
- size
text.md - lineHeight
leading.normal
- size
Figma ↔ Code property map
| Figma | Type | Code | Notes |
|---|---|---|---|
Variant | Variant | variant | Maps inline / standalone. Drives heading-wrapping (standalone wraps trigger in heading; inline does not). |
Default Expanded | Boolean | defaultExpanded | Initial open state. False for "show more" patterns; true for content that should be visible by default but collapsible for users who want to reduce noise. |
Density | Variant | density | comfortable / compact. At and below `breakpoint.sm` density compact is the canonical default for inline variant. |
Has Icon | Boolean | hasIcon | Toggles the chevron / plus-minus icon. Default true; rare to disable. |
Trigger Label | Text | triggerLabel | The button's accessible name. "Show more" / "Read details" / heading text per use case. |
Panel Content | Instance Swap | children | Swap the panel content slot — prose, form, list, custom layout. |
Icon | Instance Swap | icon | Swap the chevron glyph; defaults vary per design system. |
Motion
| Transition | Duration token |
|---|---|
expand | motion.duration.base |
collapse | motion.duration.base |
chevronRotate | motion.duration.fast |
Responsive behaviour
| Breakpoint | Change |
|---|---|
breakpoint.sm | At and below, density compact becomes the canonical default for inline-variant disclosures (narrow viewports cannot accommodate comfortable density inline). Standalone-variant disclosures retain comfortable density unless explicitly authored as compact. |
breakpoint.md | Above this width, density and variant render as authored. No layout transformation. |
Internationalisation
RTL · mirroring
Trigger inline-content order reverses logically — the icon moves from inline-end (visual right in LTR) to inline-end (visual left in RTL) via logical positioning. Panel content inherits document direction. Chevron rotation is direction-neutral (down/up arrows are symmetric); plus-minus glyphs are also direction-neutral. Standalone-variant heading-wrapping is direction-neutral.
Text expansion
Trigger label wraps to additional lines under heavy expansion (DE / RU / FI). Panel content follows its own text-flow. Density compact risks crowding long-text trigger labels; density comfortable is the safer default in long-text locales. Multi-line trigger labels behave correctly with the icon — chevron aligns with the first line by canonical convention.
Variants, properties, states
Variants
Structurally different versions of the component.
inlinestandalone Properties
The same component, parameterised.
| Property | Type |
|---|---|
defaultExpanded | boolean |
density | comfortable | compact |
hasIcon | boolean |
States
Browser/user-driven (interactive) vs. app-driven (data).
| Kind | States |
|---|---|
interactive | hoverfocus-visibleactivedisabled |
data | closedexpandingexpandedcollapsing |
State transitions
| From | To | Trigger |
|---|---|---|
closed | expanding | User activates the trigger (Enter / Space / click). `aria-expanded` flips to true; the panel begins its enter animation. |
expanding | expanded | The expand animation completes (or, under prefers-reduced-motion reduce, immediately). The panel is fully visible and reachable by keyboard via Tab. |
expanded | collapsing | User activates the trigger again (toggle is symmetric on Disclosure — unlike single-mode Accordion which may have non-collapsible behaviour). |
collapsing | closed | The collapse animation completes (or immediately under reduced motion). The panel is removed from the accessibility tree and from keyboard tab order via `hidden` or `display: none`. |
Figma↔Code mismatches
- 01 Figma
A "Show more" link drawn for inline disclosure in body prose
CodeA `<button>` toggling visibility of inline content
ConsequenceDesigners may use link-styling for "Show more" affordances (looks like other inline links in prose). Implementations following the Figma file ship `<a>` with click handlers — semantically wrong (anchors navigate, disclosures toggle), breaks middle-click expectations, and SR users hear "link" with no toggle-state cue.
CorrectUse `<button>` with disclosure semantics (`aria-expanded`, `aria-controls`). Visual styling may converge with link styling (underlined, accent colour) but the underlying element is a button. Document the visual-vs-semantic distinction in mismatches.
- 02 Figma
Disclosure drawn with chevron rotation but no panel-height transition
CodeBoth chevron rotation AND panel height-transition animate together
ConsequenceDesigners animate the chevron in mocks but the panel appears instantly (Figma cannot easily mock smooth height-transitions). Developers shipping faithful-to-mock get jumpy panel transitions; SR users get no announcement while the visible content shifts.
CorrectBoth the chevron rotation and the panel height-transition share the same duration token. Implementations using `[hidden]` toggle without animation are valid for `prefers-reduced-motion: reduce` (the canonical reducedMotionFallback is `instant`).
- 03 Figma
Standalone disclosure drawn without heading semantics
CodeStandalone disclosure with the trigger wrapped in a heading element
ConsequenceDesigners draw a "section" with a click-to-expand affordance but treat it as plain UI rather than as a document structural element. Implementations skip the heading wrapper; SR users navigating by heading miss the collapsible region's existence.
CorrectFor standalone disclosures representing document content regions (an FAQ entry, a settings section, a CSV import step), wrap the trigger in a heading element of the appropriate level. For inline disclosures in prose ("Show more" inside a paragraph), the trigger is not heading- wrapped. Document the variant-driven structural choice.
- 04 Figma
Disclosure used for a single Accordion-of-one-item
CodeA standalone Disclosure component, not Accordion
ConsequenceDesigners compose an Accordion with a single item for visual consistency with multi-item accordions elsewhere on the page. Developers ship an Accordion with one item; the user sees a collapsible region but the surrounding ARIA structure is wrong (no list semantics for a single item, no shared keyboard navigation that Accordion provides for multiple items).
CorrectA single collapsible region is a Disclosure, not an Accordion-of-one. Use Disclosure when there's only one collapsible region; use Accordion when there are multiple disclosures grouped with shared keyboard navigation (ArrowDown / Up between triggers).
Common mistakes
#disclosure-no-aria-expanded
Trigger missing `aria-expanded` toggle
The button has no `aria-expanded` attribute. Visually content reveals; SR users hear "button" with no state cue. Icon rotation alone is invisible to non-sighted users.
`aria-expanded="true"` on the button when the panel is open, `false` when closed. Always pair with `aria-controls` referencing the panel's id. Style the icon from `[aria-expanded="true"]` rather than introducing a parallel `data-expanded` attribute.
#disclosure-as-link
Disclosure trigger implemented as `<a>`
The trigger is an anchor element (because it looks like a link, or because the developer copy-pasted from another "Show more" link). Middle-click opens an empty page; copy-link captures `#`; semantically wrong because anchors navigate, disclosures toggle.
Use `<button type="button">` for disclosure triggers. The visual treatment may match link styling; the underlying element is a button. The semantic distinction (link navigates, button performs in-page action) applies here as it does for Button vs Link generally.
#disclosure-no-aria-controls
`aria-controls` not wired to the panel
Trigger has `aria-expanded` but no `aria-controls`. SR users know the trigger is expanded/collapsed but cannot navigate to the disclosed content directly. The relationship between trigger and panel is implicit (visual proximity) rather than explicit.
`aria-controls` references the panel's id. Pair with the panel's `aria-labelledby` referencing the trigger's id for the bidirectional relationship. Mature primitives (Radix, React Aria) wire this automatically.
#disclosure-icon-only-state-cue
Expansion state shown only via icon rotation
The chevron rotates on expand, but `aria-expanded` is missing or stuck. Sighted users see the state; SR users do not. The icon is decorative; without `aria-expanded` it carries the entire state-meaning load.
`aria-expanded` is the source of truth. The icon visualises the state. Style icon rotation from `[aria-expanded="true"]`. The icon is `aria-hidden`.
#disclosure-anchor-link-no-expand
Page anchor link to disclosed content does not expand
A link elsewhere in the document points at content inside a closed disclosure (e.g. `href="#shipping-policy"`). The page scrolls to the panel but the panel is collapsed. Anchor navigation without auto-expand is a dead-end for the user.
On hashchange, expand the disclosure containing the target. Listener finds the disclosure containing the target id and toggles it open before scrolling. Same-pattern fix as `accordion-anchor-link-no-expand`; the canonical contract spans both Accordion and Disclosure.
Accessibility hints
| Slot | Accessibility hint | |
|---|---|---|
trigger | Real `<button>` — never a `<div>` with click handler nor an `<a>` (anchors navigate; disclosures toggle in-page state). Carries `aria-expanded="true|false"` and `aria-controls` referencing the panel's id. Standalone disclosures may be wrapped in a heading element when the disclosure represents a content region; inline disclosures (within prose) are not heading-wrapped. | |
icon | Decorative — `aria-hidden="true"`. State is communicated through `aria-expanded` on the trigger; the icon is visual reinforcement only. | |
panel | Apply `aria-labelledby` referencing the trigger's id (or `aria-label` if the trigger has no useful text content for labelling). For standalone disclosures with regional content, `role="region"` is appropriate; for inline disclosures in prose, no role needed (the content retains its native semantics). Hide via `hidden` attribute or `display: none` when collapsed (not just zero-height) so SR users do not encounter ghost content. |