Accordion and Collapsible
Accordion and Collapsible: anatomy, keyboard behavior, state, and theming in Askr.
Example
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Collapsible, CollapsibleContent, CollapsibleTrigger } from '@askrjs/themes/components';
<>
<Accordion type="single" collapsible>
<AccordionItem value="billing">
<AccordionTrigger>How billing works</AccordionTrigger>
<AccordionContent>Usage is calculated per workspace.</AccordionContent>
</AccordionItem>
</Accordion>
<Collapsible open={detailsOpen()} onOpenChange={setDetailsOpen}>
<CollapsibleTrigger>Project details</CollapsibleTrigger>
<CollapsibleContent><ProjectDetails /></CollapsibleContent>
</Collapsible>
</>Expand one panel at a time and inspect its state.
Published props
Generated from the TypeScript declarations shipped by the installed package. Named types in the Type column define the accepted values.
AccordionContentAsChildProps
Import from @askrjs/ui/accordion.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
forceMount | forceMount?: boolean | undefined; | — | — |
ref | ref?: Ref<Element>; | — | — |
AccordionContentOwnProps
Import from @askrjs/ui/accordion.
| Prop | Type | Default | Description |
|---|---|---|---|
forceMount | forceMount?: boolean | undefined; | — | — |
AccordionContentProps
Import from @askrjs/ui/accordion.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
forceMount | forceMount?: boolean | undefined; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
AccordionHeaderAsChildProps
Import from @askrjs/ui/accordion.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
ref | ref?: Ref<Element>; | — | — |
AccordionHeaderProps
Import from @askrjs/ui/accordion.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLHeadingElement>; | — | — |
AccordionItemOwnProps
Import from @askrjs/ui/accordion.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
value | value: string; | — | — |
AccordionItemProps
Import from @askrjs/ui/accordion.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
value | value: string; | — | — |
AccordionMultipleProps
Import from @askrjs/ui/accordion.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | Supports literal, nested, array-mapped, and `For`-rendered descendants. |
collapsible | collapsible?: boolean | undefined; | — | — |
defaultValue | defaultValue?: string[] | undefined; | — | — |
id | id?: string | undefined; | — | — |
loop | loop?: boolean | undefined; | — | — |
onValueChange | onValueChange?: ((value: string[]) => void) | undefined; | — | — |
orientation | orientation?: AccordionOrientation | undefined; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
type | type: "multiple"; | — | — |
value | value?: string[] | undefined; | — | — |
AccordionProps
Import from @askrjs/ui/accordion.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | Supports literal, nested, array-mapped, and `For`-rendered descendants. |
collapsible | collapsible?: boolean | undefined; | — | — |
defaultValue | defaultValue?: string | string[] | undefined; | — | — |
id | id?: string | undefined; | — | — |
loop | loop?: boolean | undefined; | — | — |
onValueChange | onValueChange?: ((value: string[]) => void) | ((value: string) => void) | undefined; | — | — |
orientation | orientation?: AccordionOrientation | undefined; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
type | type?: "single" | "multiple" | undefined; | — | — |
value | value?: string | string[] | undefined; | — | — |
AccordionSingleProps
Import from @askrjs/ui/accordion.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | Supports literal, nested, array-mapped, and `For`-rendered descendants. |
collapsible | collapsible?: boolean | undefined; | — | — |
defaultValue | defaultValue?: string | undefined; | — | — |
id | id?: string | undefined; | — | — |
loop | loop?: boolean | undefined; | — | — |
onValueChange | onValueChange?: ((value: string) => void) | undefined; | — | — |
orientation | orientation?: AccordionOrientation | undefined; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
type | type?: "single" | undefined; | — | — |
value | value?: string | undefined; | — | — |
AccordionTriggerAsChildProps
Import from @askrjs/ui/accordion.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
onPress | onPress?: ((event: PressEvent) => void) | undefined; | — | — |
ref | ref?: Ref<Element>; | — | — |
type | type?: undefined; | — | — |
AccordionTriggerProps
Import from @askrjs/ui/accordion.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
onPress | onPress?: ((event: PressEvent) => void) | undefined; | — | — |
ref | ref?: Ref<HTMLButtonElement>; | — | — |
type | type?: "button" | "submit" | "reset" | undefined; | — | — |
CollapsibleContentAsChildProps
Import from @askrjs/ui/collapsible.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | Render as child element instead of div |
children | children: JSXElement; | — | Child content |
forceMount | forceMount?: boolean | undefined; | — | Force mount even when closed (for animation) |
ref | ref?: Ref<Element>; | — | Ref forwarding |
CollapsibleContentProps
Import from @askrjs/ui/collapsible.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | Render as child element instead of div |
children | children?: unknown; | — | Child content |
forceMount | forceMount?: boolean | undefined; | — | Force mount even when closed (for animation) |
ref | ref?: Ref<HTMLDivElement>; | — | Ref forwarding |
CollapsibleProps
Import from @askrjs/ui/collapsible.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | Child components (Trigger, Content) |
defaultOpen | defaultOpen?: boolean | undefined; | — | Uncontrolled default open state |
disabled | disabled?: boolean | undefined; | — | Whether the collapsible is disabled |
id | id?: string | undefined; | — | Stable caller-provided identity used for ARIA linking when available |
onOpenChange | onOpenChange?: ((open: boolean) => void) | undefined; | — | Callback when open state changes |
open | open?: boolean | undefined; | — | Controlled open state |
CollapsibleTriggerAsChildProps
Import from @askrjs/ui/collapsible.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | Render as child element instead of button |
children | children: JSXElement; | — | Child content |
ref | ref?: Ref<Element>; | — | Ref forwarding |
CollapsibleTriggerProps
Import from @askrjs/ui/collapsible.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | Render as child element instead of button |
children | children?: unknown; | — | Child content |
ref | ref?: Ref<HTMLButtonElement>; | — | Ref forwarding |
Purpose
Accordion groups several related disclosures — think an FAQ list or a settings panel — and manages roving keyboard focus and open state across all of them at once. Collapsible is the single-region primitive Accordion is effectively built from: reach for it when you have one thing to show or hide, and reach for Accordion once you have a list of items sharing that same behavior.
Install and import
Install `@askrjs/ui` for the headless behavior and `@askrjs/themes` for the styled defaults. Pull Accordion pieces from `@askrjs/ui/accordion` (headless) or `@askrjs/themes/accordion` (styled), and Collapsible pieces from `@askrjs/ui/collapsible` or `@askrjs/themes/collapsible` — the two components ship as separate subpaths in both packages, not one combined module.
Live examples
The example composes the themed Accordion and Collapsible directly. Once it is running in your own app, open devtools and watch `data-state` and `aria-expanded` change as you toggle an item — those attributes are the styling and testing contract, so confirm your markup produces them before layering custom CSS on top.
Anatomy
Accordion composes an Accordion root around one or more AccordionItem elements, each holding an AccordionHeader/AccordionTrigger pair and an AccordionContent panel; the item's `value` prop is what open state actually tracks. Collapsible is flatter — just Collapsible, CollapsibleTrigger, and CollapsibleContent — because there's only ever one region, so there's no item wrapper or `value` to assign.
State model
Accordion runs in one of two modes: `type="single"` (the default) tracks a single open `value` string, while `type="multiple"` tracks a `string[]`. Both accept `value`/`onValueChange` for controlled use or `defaultValue` for uncontrolled use, plus `collapsible` (whether the last open item in single mode can close) and `loop` for keyboard wraparound. Collapsible needs none of that type juggling — it's just `open`/`defaultOpen`/`onOpenChange` as a plain boolean.
Keyboard and accessibility
Triggers in both components carry `role="button"` semantics with `aria-expanded` and `aria-controls`, and respond to Enter or Space to toggle open state — behavior the `COLLAPSIBLE_A11Y_CONTRACT` and `ACCORDION_A11Y_CONTRACT` constants codify directly. Accordion goes a step further, wrapping its panels in `role="region"` and giving triggers arrow-key roving focus governed by `orientation` and `loop`, since a set of triggers benefits from list-style navigation between them. Collapsible skips that roving-focus layer entirely, since managing a single trigger leaves nothing to navigate across.
Styling and tokens
Both families expose `data-slot`, `data-state`, and `data-disabled` (Accordion adds `data-orientation`) so CSS can target open, closed, and disabled states without touching JavaScript. Content unmounts when closed by default in both; pass `forceMount` on AccordionContent or CollapsibleContent if you need the panel present in the DOM to drive a height-based CSS transition.
API
AccordionItem takes a required `value` plus `disabled`; AccordionTrigger takes the same button-like props as other interactive primitives, including `asChild`. Collapsible's root takes `open`, `defaultOpen`, `onOpenChange`, and `disabled` — there's no `type` prop because it never tracks more than one boolean, unlike Accordion.
Edge cases
With `collapsible={false}` (the default for single-type Accordion), at least one item always stays open, so don't build a "collapse all" affordance against that configuration. `disabled` on an AccordionItem blocks its trigger but leaves the item in the DOM, so a screen reader user still encounters it — if every item ends up disabled, consider hiding the whole accordion instead of leaving a fully inert one on screen.
Related pages
See Headless versus Themed for how the `@askrjs/ui`/`@askrjs/themes` split works in general, and Focus and Dismissal for the keyboard and focus utilities both primitives are built on. The Combobox and Command page covers a different disclosure shape if what you actually need is a filterable list rather than a static one.