Components / disclosure

Collapsible

This is one of two components that look like they need a binding and do not — the other is RadioGroup. A component moves up a tier by taking a native element, never by adding script to the core.

Rendered from the package · open on its own

Summary

One trigger, one region.

APG patternDisclosure
Built onRadix
Statusstable
Since0.2.0

Use it to take something off the screen that most readers do not need.

<Collapsible summary="Advanced">…</Collapsible>

Renamed from Disclosure in 0.2.0. The APG calls the pattern disclosure and it still does — that row above is unchanged. What moved is the component's own name, because disclosure names the behavior rather than the thing, and a reader who has not read the APG cannot guess what it draws. Collapsible is what the primitive underneath is called.

The anatomy moved with it: data-se="disclosure" is now data-se="collapsible". That is the breaking half — it is what a Vue or Astro consumer writes, and there is no alias.

Anatomy

A <details data-se="disclosure"> with a <summary> as the trigger.

PartElementRequired
content<radixcollapsible.content>yes
indicator<svg>yes
trigger<radixcollapsible.trigger>yes

Properties

PropertyValuesDefaultWhat it does
labelReact.ReactNoderequiredThe trigger's visible text.
openbooleannonePassing this makes the disclosure controlled. Without it the native <details> keeps its own state and needs no JavaScript at all.
defaultOpenbooleannoneStarts open. Use it for a section most readers will want, rather than for one they must not miss — a required field inside a closed region is invisible.
onOpenChange(open: boolean) => voidnoneCalled when the reader toggles it.
childrenReact.ReactNodenoneThe region's contents. Revealed on open; still in the DOM when closed, so anything focusable inside is reachable — do not put a form control in a section the reader may never open.

States

StateExpressed as
Focused:focus-visible
Hover:hover
Open or closeddata-state

Behavior

It needs no script. The anatomy is <details>, so open and close come from the platform. Radix supplies animated height and nothing else.

Accessibility

<summary> is a button with aria-expanded, from the element. Enter and Space both work without a line of JavaScript.

Do not hide anything the reader must act on. A required field or an error inside a collapsed region is invisible, and no amount of styling fixes that.

Keyboard

KeyResult
Enter / SpaceToggles the region — from <summary>, with no script

Roles, states and properties

AttributeSet by
aria-hiddenalways true

Focus

The summary is the only stop. When the region closes, focus stays on the summary rather than vanishing with the content.

Screen reader

Not recorded as verified. Stage 4 of the readiness ladder — driving each component with VoiceOver and NVDA — is unstarted across the package, and no component should claim otherwise until docs/journal/ records the session that did it.

Contrast and target size

npm run check:contrast audits every color pair this component uses across all four skins, light and dark, and fails the build on a shortfall. Two waivers are on the record in docs/audit/DEFERRED.md.

It sets no target size of its own; anything interactive inside it brings its own.

Tested

  • test/browser/upstream-natives.spec.mjs

Known gaps

  • 3 upstream refs — their image-description example
  • 5 upstream refs — a navigation disclosure set — a composition we do not ship

Appearance

TokenUsed for
--se-accentcolor
--se-borderborder-block-end
--se-border-widthborder-block-end
--se-control-hmin-block-size
--se-duration-fasttransition
--se-duration-instanttransition
--se-ease-standardtransition
--se-focus-coloroutline
--se-focus-widthoutline, outline-offset
--se-icon-smblock-size, inline-size
--se-leading-bodyline-height
--se-radius-smborder-radius
--se-space-3gap, padding-block
--se-space-4padding-block-end
--se-textcolor
--se-text-mutedcolor
--se-text-smfont-size

Motion

Height animates over --se-duration-fast when the React binding is used. Without it the region snaps, which is correct rather than degraded — a snap is the platform's answer and it is instant.

Content

The summary says what is inside, closed. Advanced, Shipping details. A summary that reads Click to expand has spent the one line the reader gets on nothing.

Usage

Reach for the React binding when you need controlled state or an animated open; otherwise <details> is less machinery for the same result.

One section, and it knows about nothing else. A disclosure has no opinion about any other disclosure on the page: no shared heading level, no rule about how many may be open, no group relationship. That is the whole difference from Accordion, and it is why three of these stacked are not an accordion — a screen-reader user hears three unrelated toggles instead of one set they can skim by heading.

When not to use it. For a set of sections at one heading level, use Accordion. If the sections are alternatives and only one is ever relevant, use Tabs.

Notes

This is one of two components that look like they need a binding and do not — the other is RadioGroup. A component moves up a tier by taking a native element, never by adding script to the core.