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/themes/accordion.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
forceMount | forceMount?: boolean | undefined; | — | — |
ref | ref?: Ref<Element>; | — | — |
AccordionContentOwnProps
Import from @askrjs/themes/accordion.
| Prop | Type | Default | Description |
|---|---|---|---|
forceMount | forceMount?: boolean | undefined; | — | — |
AccordionContentProps
Import from @askrjs/themes/accordion.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
forceMount | forceMount?: boolean | undefined; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
AccordionHeaderAsChildProps
Import from @askrjs/themes/accordion.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
ref | ref?: Ref<Element>; | — | — |
AccordionHeaderProps
Import from @askrjs/themes/accordion.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLHeadingElement>; | — | — |
AccordionItemOwnProps
Import from @askrjs/themes/accordion.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
value | value: string; | — | — |
AccordionItemProps
Import from @askrjs/themes/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/themes/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/themes/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/themes/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/themes/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/themes/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/themes/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/themes/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/themes/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/themes/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/themes/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 expose aria-expanded and aria-controls and support keyboard activation. Accordion additionally coordinates focus among its triggers according to orientation and loop; Collapsible owns a single disclosure. Test the rendered relationships, focus movement and open state through those public components.
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.