# Accordion and Collapsible

> Accordion and Collapsible: anatomy, keyboard behavior, state, and theming in Askr.

Source: [https://askrjs.com/docs/components/accordion-and-collapsible](https://askrjs.com/docs/components/accordion-and-collapsible)

Status: stable. Packages: @askrjs/ui/accordion, @askrjs/ui/collapsible, @askrjs/themes/accordion, @askrjs/themes/collapsible.

**Published packages are authoritative.** Examples may lag behind a published contract. When guidance differs, verify the exports and TypeScript declarations in your installed package, then file an issue.

## Example

```tsx
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>
</>
```

## Published props

Generated from the TypeScript declarations shipped by the installed package.

### `AccordionContentAsChildProps`

Import from `@askrjs/ui/accordion`.

- `asChild: true;`
- `children: JSXElement;`
- `forceMount?: boolean | undefined;`
- `ref?: Ref<Element>;`

### `AccordionContentOwnProps`

Import from `@askrjs/ui/accordion`.

- `forceMount?: boolean | undefined;`

### `AccordionContentProps`

Import from `@askrjs/ui/accordion`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `forceMount?: boolean | undefined;`
- `ref?: Ref<HTMLDivElement>;`

### `AccordionHeaderAsChildProps`

Import from `@askrjs/ui/accordion`.

- `asChild: true;`
- `children: JSXElement;`
- `ref?: Ref<Element>;`

### `AccordionHeaderProps`

Import from `@askrjs/ui/accordion`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `ref?: Ref<HTMLHeadingElement>;`

### `AccordionItemOwnProps`

Import from `@askrjs/ui/accordion`.

- `children?: unknown;`
- `disabled?: boolean | undefined;`
- `value: string;`

### `AccordionItemProps`

Import from `@askrjs/ui/accordion`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `disabled?: boolean | undefined;`
- `ref?: Ref<HTMLDivElement>;`
- `value: string;`

### `AccordionMultipleProps`

Import from `@askrjs/ui/accordion`.

- `asChild?: false | undefined;`
- `children?: unknown;` — Supports literal, nested, array-mapped, and `For`-rendered descendants.
- `collapsible?: boolean | undefined;`
- `defaultValue?: string[] | undefined;`
- `id?: string | undefined;`
- `loop?: boolean | undefined;`
- `onValueChange?: ((value: string[]) => void) | undefined;`
- `orientation?: AccordionOrientation | undefined;`
- `ref?: Ref<HTMLDivElement>;`
- `type: "multiple";`
- `value?: string[] | undefined;`

### `AccordionProps`

Import from `@askrjs/ui/accordion`.

- `asChild?: false | undefined;`
- `children?: unknown;` — Supports literal, nested, array-mapped, and `For`-rendered descendants.
- `collapsible?: boolean | undefined;`
- `defaultValue?: string | string[] | undefined;`
- `id?: string | undefined;`
- `loop?: boolean | undefined;`
- `onValueChange?: ((value: string[]) => void) | ((value: string) => void) | undefined;`
- `orientation?: AccordionOrientation | undefined;`
- `ref?: Ref<HTMLDivElement>;`
- `type?: "single" | "multiple" | undefined;`
- `value?: string | string[] | undefined;`

### `AccordionSingleProps`

Import from `@askrjs/ui/accordion`.

- `asChild?: false | undefined;`
- `children?: unknown;` — Supports literal, nested, array-mapped, and `For`-rendered descendants.
- `collapsible?: boolean | undefined;`
- `defaultValue?: string | undefined;`
- `id?: string | undefined;`
- `loop?: boolean | undefined;`
- `onValueChange?: ((value: string) => void) | undefined;`
- `orientation?: AccordionOrientation | undefined;`
- `ref?: Ref<HTMLDivElement>;`
- `type?: "single" | undefined;`
- `value?: string | undefined;`

### `AccordionTriggerAsChildProps`

Import from `@askrjs/ui/accordion`.

- `asChild: true;`
- `children: JSXElement;`
- `disabled?: boolean | undefined;`
- `onPress?: ((event: PressEvent) => void) | undefined;`
- `ref?: Ref<Element>;`
- `type?: undefined;`

### `AccordionTriggerProps`

Import from `@askrjs/ui/accordion`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `disabled?: boolean | undefined;`
- `onPress?: ((event: PressEvent) => void) | undefined;`
- `ref?: Ref<HTMLButtonElement>;`
- `type?: "button" | "submit" | "reset" | undefined;`

### `CollapsibleContentAsChildProps`

Import from `@askrjs/ui/collapsible`.

- `asChild: true;` — Render as child element instead of div
- `children: JSXElement;` — Child content
- `forceMount?: boolean | undefined;` — Force mount even when closed (for animation)
- `ref?: Ref<Element>;` — Ref forwarding

### `CollapsibleContentProps`

Import from `@askrjs/ui/collapsible`.

- `asChild?: false | undefined;` — Render as child element instead of div
- `children?: unknown;` — Child content
- `forceMount?: boolean | undefined;` — Force mount even when closed (for animation)
- `ref?: Ref<HTMLDivElement>;` — Ref forwarding

### `CollapsibleProps`

Import from `@askrjs/ui/collapsible`.

- `children?: unknown;` — Child components (Trigger, Content)
- `defaultOpen?: boolean | undefined;` — Uncontrolled default open state
- `disabled?: boolean | undefined;` — Whether the collapsible is disabled
- `id?: string | undefined;` — Stable caller-provided identity used for ARIA linking when available
- `onOpenChange?: ((open: boolean) => void) | undefined;` — Callback when open state changes
- `open?: boolean | undefined;` — Controlled open state

### `CollapsibleTriggerAsChildProps`

Import from `@askrjs/ui/collapsible`.

- `asChild: true;` — Render as child element instead of button
- `children: JSXElement;` — Child content
- `ref?: Ref<Element>;` — Ref forwarding

### `CollapsibleTriggerProps`

Import from `@askrjs/ui/collapsible`.

- `asChild?: false | undefined;` — Render as child element instead of button
- `children?: unknown;` — Child content
- `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.

## Documentation navigation

[Previous](https://askrjs.com/docs/components/advanced-layout/index.md) | [Next](https://askrjs.com/docs/components/progress/index.md)
