Askr documentation
UI & Components

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>
</>
Try Accordion

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.

PropTypeDefaultDescription
asChildasChild: true;
childrenchildren: JSXElement;
forceMountforceMount?: boolean | undefined;
refref?: Ref<Element>;

AccordionContentOwnProps

Import from @askrjs/ui/accordion.

PropTypeDefaultDescription
forceMountforceMount?: boolean | undefined;

AccordionContentProps

Import from @askrjs/ui/accordion.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;
forceMountforceMount?: boolean | undefined;
refref?: Ref<HTMLDivElement>;

AccordionHeaderAsChildProps

Import from @askrjs/ui/accordion.

PropTypeDefaultDescription
asChildasChild: true;
childrenchildren: JSXElement;
refref?: Ref<Element>;

AccordionHeaderProps

Import from @askrjs/ui/accordion.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;
refref?: Ref<HTMLHeadingElement>;

AccordionItemOwnProps

Import from @askrjs/ui/accordion.

PropTypeDefaultDescription
childrenchildren?: unknown;
disableddisabled?: boolean | undefined;
valuevalue: string;

AccordionItemProps

Import from @askrjs/ui/accordion.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;
disableddisabled?: boolean | undefined;
refref?: Ref<HTMLDivElement>;
valuevalue: string;

AccordionMultipleProps

Import from @askrjs/ui/accordion.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;Supports literal, nested, array-mapped, and `For`-rendered descendants.
collapsiblecollapsible?: boolean | undefined;
defaultValuedefaultValue?: string[] | undefined;
idid?: string | undefined;
looploop?: boolean | undefined;
onValueChangeonValueChange?: ((value: string[]) => void) | undefined;
orientationorientation?: AccordionOrientation | undefined;
refref?: Ref<HTMLDivElement>;
typetype: "multiple";
valuevalue?: string[] | undefined;

AccordionProps

Import from @askrjs/ui/accordion.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;Supports literal, nested, array-mapped, and `For`-rendered descendants.
collapsiblecollapsible?: boolean | undefined;
defaultValuedefaultValue?: string | string[] | undefined;
idid?: string | undefined;
looploop?: boolean | undefined;
onValueChangeonValueChange?: ((value: string[]) => void) | ((value: string) => void) | undefined;
orientationorientation?: AccordionOrientation | undefined;
refref?: Ref<HTMLDivElement>;
typetype?: "single" | "multiple" | undefined;
valuevalue?: string | string[] | undefined;

AccordionSingleProps

Import from @askrjs/ui/accordion.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;Supports literal, nested, array-mapped, and `For`-rendered descendants.
collapsiblecollapsible?: boolean | undefined;
defaultValuedefaultValue?: string | undefined;
idid?: string | undefined;
looploop?: boolean | undefined;
onValueChangeonValueChange?: ((value: string) => void) | undefined;
orientationorientation?: AccordionOrientation | undefined;
refref?: Ref<HTMLDivElement>;
typetype?: "single" | undefined;
valuevalue?: string | undefined;

AccordionTriggerAsChildProps

Import from @askrjs/ui/accordion.

PropTypeDefaultDescription
asChildasChild: true;
childrenchildren: JSXElement;
disableddisabled?: boolean | undefined;
onPressonPress?: ((event: PressEvent) => void) | undefined;
refref?: Ref<Element>;
typetype?: undefined;

AccordionTriggerProps

Import from @askrjs/ui/accordion.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;
disableddisabled?: boolean | undefined;
onPressonPress?: ((event: PressEvent) => void) | undefined;
refref?: Ref<HTMLButtonElement>;
typetype?: "button" | "submit" | "reset" | undefined;

CollapsibleContentAsChildProps

Import from @askrjs/ui/collapsible.

PropTypeDefaultDescription
asChildasChild: true;Render as child element instead of div
childrenchildren: JSXElement;Child content
forceMountforceMount?: boolean | undefined;Force mount even when closed (for animation)
refref?: Ref<Element>;Ref forwarding

CollapsibleContentProps

Import from @askrjs/ui/collapsible.

PropTypeDefaultDescription
asChildasChild?: false | undefined;Render as child element instead of div
childrenchildren?: unknown;Child content
forceMountforceMount?: boolean | undefined;Force mount even when closed (for animation)
refref?: Ref<HTMLDivElement>;Ref forwarding

CollapsibleProps

Import from @askrjs/ui/collapsible.

PropTypeDefaultDescription
childrenchildren?: unknown;Child components (Trigger, Content)
defaultOpendefaultOpen?: boolean | undefined;Uncontrolled default open state
disableddisabled?: boolean | undefined;Whether the collapsible is disabled
idid?: string | undefined;Stable caller-provided identity used for ARIA linking when available
onOpenChangeonOpenChange?: ((open: boolean) => void) | undefined;Callback when open state changes
openopen?: boolean | undefined;Controlled open state

CollapsibleTriggerAsChildProps

Import from @askrjs/ui/collapsible.

PropTypeDefaultDescription
asChildasChild: true;Render as child element instead of button
childrenchildren: JSXElement;Child content
refref?: Ref<Element>;Ref forwarding

CollapsibleTriggerProps

Import from @askrjs/ui/collapsible.

PropTypeDefaultDescription
asChildasChild?: false | undefined;Render as child element instead of button
childrenchildren?: unknown;Child content
refref?: 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.

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.