Collapsible
One section that shows and hides.
Example
Installation
pnpm dlx shadcn@latest add superzero11/uiset/collapsibleIt lands in components/ui/collapsible.tsx, with UISet’s root (uiset). Wrap your app in <UISet> once, and every component takes its skin.
What UISet adds
The component is UI SDK’s own, with its parts, props and names, so everything below works as UI SDK documents it. UISet adds the look and checks it:
- Styles in five skins, each in light and dark, from CSS variables you can override.
- Text at 4.5:1 contrast and control edges and focus rings at 3:1, in every skin and theme, checked with axe in tests.
- Haptic cues on phones, and sound cues when you set
soundon<UISet>. - Examples that name every control, so agents can find it (see Agents below).
The API reference and accessibility notes below are UI SDK’s, from its docs for @ui-sdk/react (Apache-2.0). Collapsible on ui-sdk.dev has more usage examples. Your coding agent can read the same page at node_modules/@ui-sdk/react/docs/components/collapsible.md.
API Reference
Props and callbacks are the same as below, as React props.
Collapsible
open?(boolean): Whether the panel is open. Pass it to control the collapsible.defaultOpen?(boolean): Whether the panel is open at first, when uncontrolled.onOpenChange?(((open: boolean, details: ChangeDetails<CollapsibleOpenChangeReason>) => void)): Runs when the panel wants to open or close.details.reasonsays why, anddetails.cancel()stops it.onOpenChangeComplete?(((open: boolean) => void)): Runs once the panel has finished opening or closing, after any animation.disabled?(boolean): Stops the trigger from opening or closing the panel. The trigger stays focusable.controller?(object): Opens and closes the panel from anywhere. Create one withcreateCollapsibleController().debug?(boolean): Logs every state change with its reason to the console, in development builds.
CollapsibleTrigger
The trigger takes no options of its own, only the props of the element it renders.
CollapsiblePanel
keepMounted?(boolean): Whether the panel stays in the page, hidden, while closed.hiddenUntilFound?(boolean): Keeps the closed panel in the page ashidden="until-found", so the browser's find in page can match its text and open it.
State
What render functions, state-based class names and slots receive. The trigger and the panel receive the same state as the collapsible.
open(boolean): Whether the panel is open.disabled(boolean): Whether the trigger ignores presses, by its owndisabledor a disabled fieldset around it. Theopenoption and the controller can still open and close it.
Reasons
details.reason says why the panel opened or closed.
trigger-press(MouseEvent | KeyboardEvent | TouchEvent): Click, tap, Enter or Space on the part that opens, closes or toggles. Arrow keys that open a menu or submenu count too. A tap on a combobox's input that opens a sheet reports itstouchend.find-in-page(Event): The browser's find in page matched hidden content.imperative(none): A controller orapicall, or a property set on a web component.
Controller
Create one with createCollapsibleController() and pass it as controller.
Every write method takes an optional { reason, event }, and returns false when the change was canceled or changed nothing.
isOpen()(() => boolean): Whether the panel is open.setOpen(open, options?)((open: boolean, options?: CollapsibleChangeOptions | undefined) => boolean): Opens or closes the panel. Returns false when the change was canceled or not possible.open(options?)((options?: CollapsibleChangeOptions | undefined) => boolean): Opens the panel.close(options?)((options?: CollapsibleChangeOptions | undefined) => boolean): Closes the panel.toggle(options?)((options?: CollapsibleChangeOptions | undefined) => boolean): Opens the panel when it's closed, and closes it when it's open.focus()(() => void): Moves focus to the trigger.
Events
Callbacks get the new state first, then the details.
| Prop | On | Receives |
|---|---|---|
onOpenChange | Collapsible | (open, details): the new state, and the reason |
onOpenChangeComplete | Collapsible | (open), after the panel's animation |
Accessibility
The collapsible follows the WAI-ARIA disclosure pattern. The trigger says whether the panel is open with aria-expanded, and points to the panel with aria-controls while it's mounted. Every line below is an automated test.
Keyboard
| Key | Does |
|---|---|
| Enter, Space | Opens or closes the panel. Focus stays on the trigger. |
| Tab | Moves from the trigger into an open panel |
Closing a panel while focus is inside it moves focus back to the trigger.
Tested Behavior
Opening and closing
- Starts closed, with the panel out of the page.
- Clicking the trigger opens the panel, and clicking again closes it.
- The trigger controls the panel.
- Generated ids start with ui-sdk-.
- It can start open.
- An opening can be canceled.
- A disabled collapsible doesn't open.
- A disabled trigger stays focusable and says it's disabled.
- A controller opens and closes it from anywhere.
Keyboard
- Enter toggles the panel and keeps focus on the trigger.
- Space toggles the panel and keeps focus on the trigger.
- Tab moves from the open trigger into the panel.
- Tab skips a closed panel.
Mounting
- With keepMounted, the closed panel stays in the page, hidden.
- With keepMounted, a class that sets display doesn't show the closed panel.
- With hiddenUntilFound, the closed panel stays findable.
- With hiddenUntilFound, a class that sets display keeps the closed panel findable.
- Finding text in the closed panel opens it.
- Following a link into the closed panel opens it.
- The browser's own find opens the closed panel.
Runs in React, Vue and web components.
Beyond the pattern's baseline:
Enhancements
- Closing the panel while focus is inside it moves focus to the trigger.
- OnOpenChangeComplete runs once the animation has finished.
- Find in page reports its own reason and opens without an animation.
- A canceled find in page keeps the panel hidden until found.
- An infinite exit animation still unmounts the panel.
- Parts report open and closed through data attributes.
Runs in React, Vue and web components.
Screen readers hear the same thing in each tested framework, recorded by a virtual screen reader:
- Announcements while using the collapsible with a keyboard.
Runs in React, Vue and web components.
Agents
UI SDK’s agent bridge finds this component by its accessible name: give it a visible label or an aria-label, and an agent can read its state and call its actions without screenshots. Building with AI agents shows how to connect one.
This is agent.snapshot() for the example above, read live from this page. Change something in the example, then read it again: the values follow.
[
{
"ref": "c8",
"component": "collapsible",
"actions": [
"setOpen",
"press"
],
"label": "Show advanced settings",
"open": false
}
]