apx-dsv0.1Local renderer · live
K
Foundations4
  • Getting started
  • Theming
  • Templates
  • Icons
  • Avatar
  • Badge
  • DataGrid
  • Scheduler
  • Stat
  • Table
  • Timeline
  • TreeView
  • Accordion
  • Alert
  • EmptyState
  • Progress
  • Skeleton
  • Spinner
  • SplashScreen
  • Toast
  • ColorPicker
  • FileUpload
  • Form
  • Rating
  • TagsInput
  • Combobox
  • Field
  • Select
  • Toggle
  • Button
  • Calendar
  • Checkbox
  • DatePicker
  • Input
  • NumberInput
  • Radio
  • Slider
  • Switch
  • Textarea
  • AppShell
  • Div
  • Divider
  • Sidebar
  • Stack
  • Typography
  • Image
  • Breadcrumbs
  • Carousel
  • NavigationMenu
  • Pagination
  • Stepper
  • Tabs
  • Toolbar
  • CommandPalette
  • Confirm
  • Drawer
  • HoverCard
  • Menu
  • Modal
  • Popover
  • Tooltip
  • Icon
  • Card
  • PricingCard
60 componentsapx-ds/renderer
Accordion
Variant↳ other

Disclosure

Accordion

Disclosure-list primitive — a stack of collapsible sections. Single or multi-open, ARIA-pattern keyboard nav, CSS-grid auto-height transition (no JS measurement).

Overview

<Accordion /> is the canonical disclosure list primitive in apx-ds — a stack of collapsible sections that share state. It's the first DS component that manages a list of toggleable values, and the first compound primitive whose subparts coordinate through a pair of contexts (root + per-item).

Reach for it whenever a screen needs a stacked, expandable list: FAQ pages, settings panels, sidebars of categorized navigation, or any disclosure UX where one section at a time (or several, at the user's choice) is open.

Overview — settings panel with icons

Loading preview…
Overview.tsx

Why this exists

Three jobs land in this primitive that other disclosure surfaces (Tabs, Navigation drawers, Tree views) will reuse:

  1. Compound state via context. The root owns the open set; the trigger toggles membership; the content reads its own state. Subparts coordinate without prop drilling.
  2. CSS-grid auto-height transitions. The open/close animation lives entirely in CSS — grid-template-rows: 0fr → 1fr plus min-h-0 on the inner element. Zero JavaScript height measurement, zero ResizeObserver, zero scrollHeight reads. Smooth on long content, on dynamically-resizing content, and on every modern browser since 2024.
  3. W3C ARIA accordion pattern. Each trigger is a real <button> with aria-expanded + aria-controls; each content is a role="region" labelled by its trigger; arrow keys cycle through enabled triggers; Home/End jump to first/last.

Modes

<Accordion> ships two state shapes via the type prop:

  • type="single" (default) — value is a string. One item open at a time. Setting collapsible={true} (the DS default) lets the user click the open item to close it; set collapsible={false} to enforce "one section always open" (e.g. a settings panel with a pinned first row).
  • type="multiple" — value is a string[]. Each item toggles independently. The collapsible prop is ignored in this mode (multi-open is collapsible by construction).

Anatomy

tsx
┌────────────────────────────────────────────────────────────┐
│ Accordion                                                  │
│ ┌──────────────────────────────────────────────────────┐  │
│ │ Accordion.Item                                       │  │
│ │ ┌────────────────────────────────────────────────┐   │  │
│ │ │ Accordion.Trigger  [leftIcon]  Label   [▾]    │   │  │
│ │ └────────────────────────────────────────────────┘   │  │
│ │ ┌────────────────────────────────────────────────┐   │  │
│ │ │ Accordion.Content (CSS-grid animated)         │   │  │
│ │ │ ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔  │   │  │
│ │ │  region(role="region", aria-labelledby=...)   │   │  │
│ │ └────────────────────────────────────────────────┘   │  │
│ └──────────────────────────────────────────────────────┘  │
│ … more Items …                                            │
└────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ Accordion                                                  │
│ ┌──────────────────────────────────────────────────────┐  │
│ │ Accordion.Item                                       │  │
│ │ ┌────────────────────────────────────────────────┐   │  │
│ │ │ Accordion.Trigger  [leftIcon]  Label   [▾]    │   │  │
│ │ └────────────────────────────────────────────────┘   │  │
│ │ ┌────────────────────────────────────────────────┐   │  │
│ │ │ Accordion.Content (CSS-grid animated)         │   │  │
│ │ │ ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔  │   │  │
│ │ │  region(role="region", aria-labelledby=...)   │   │  │
│ │ └────────────────────────────────────────────────┘   │  │
│ └──────────────────────────────────────────────────────┘  │
│ … more Items …                                            │
└────────────────────────────────────────────────────────────┘

Variants × Colors

The 4 × 7 matrix lives on Accordion.Item. Variant changes the chrome (continuous border in solid, per-item cards in outline, tinted in soft, chrome-less in ghost); color flows through soft backgrounds and outline borders. solid and ghost stay color-neutral by design — bold-color chrome on a continuous list (or a chrome-less context) reads as noise.

VariantItem chromeWhen to reach for it
solidcontinuous border + horizontal dividersDefault. FAQ pages, settings groups.
outlineeach item is its own bordered cardSpaced lists, card-like disclosure decks.
softtinted card per item (color-subtle bg)Brand-tinted FAQ, themed disclosure groups.
ghostno chrome, only the trigger row carries weightInline disclosure inside Card / settings.

Sizes

SizeTrigger paddingContent paddingFontChevron
smpx-3 py-2px-3 pb-3text-sm14px
mdpx-4 py-3px-4 pb-4text-base16px
lgpx-5 py-4px-5 pb-5text-lg20px

Examples

Default — single mode, solid variant

Loading preview…
Basic.tsx

Multiple mode — independent toggles

Loading preview…
Multiple.tsx

collapsible=true vs false (single mode)

Loading preview…
Collapsible.tsx

Controlled — parent owns value

Loading preview…
Controlled.tsx

All four variants

Loading preview…
Variants.tsx

Three sizes (sm / md / lg)

Loading preview…
Sizes.tsx

Seven colors × soft variant

Loading preview…
Colors.tsx

Leading icons on each trigger

Loading preview…
WithIcons.tsx

Chevron on the logical start

Loading preview…
IconPositionStart.tsx

Per-item disable

Loading preview…
DisabledItem.tsx

Rich content (lists, code, prose)

Loading preview…
LongContent.tsx

Canonical FAQ pattern

Loading preview…
FAQ.tsx

Accordion inside Accordion

Loading preview…
Nested.tsx

Props

Accordion (root)

PropTypeDefaultNotes
type'single' | 'multiple''single'Discriminates value / defaultValue / onValueChange between string/array.
valuestring | string[]—Controlled value. Pair with onValueChange.
defaultValuestring | string[]'' / []Uncontrolled initial value.
onValueChange(value: string | string[]) => void—Fires on every change (controlled or uncontrolled).
collapsiblebooleantrueSingle-mode only. true lets the user close the open item by re-clicking.
disabledbooleanfalseDisables every item. Per-item disabled overrides.
variant'solid' | 'outline' | 'soft' | 'ghost''solid'Stylistic family. Responsive via { base, md } shape.
size'sm' | 'md' | 'lg''md'Trigger padding + content padding + font + chevron.
color'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'neutral''neutral'Drives soft backgrounds + outline borders.
iconPosition'start' | 'end''end'Chevron placement (logical, RTL-aware).
classNamestring—Merged via tailwind-merge. Last-wins.
sxSx—Theme-aware inline style.

Accordion.Item

PropTypeDefaultNotes
valuestringrequiredUnique key identifying this item in the parent's value.
disabledbooleanfalseDisable just this item. Overrides root disabled=false.

Accordion.Trigger

PropTypeDefaultNotes
leftIconReactNode—Decorative icon at the logical start. Always aria-hidden.

Accordion.Content

Standard <div> props plus sx. Renders as a role="region" labelled by its trigger.

Accessibility

  • W3C ARIA Accordion pattern. Each trigger is <button> with aria-expanded + aria-controls; each content is role="region" with aria-labelledby.
  • Keyboard navigation. ArrowDown / ArrowUp cycles focus between sibling triggers (wrapping at the edges), Home / End jumps to first / last enabled trigger. Disabled triggers are skipped. Enter / Space activate via native <button> semantics.
  • Decorative icons. The chevron and any leftIcon are wrapped in aria-hidden="true" — the trigger label is the accessible name.
  • Reduced-motion. The CSS transition shortens to ~120ms under @media (prefers-reduced-motion: reduce), plus the chevron rotation disables entirely. Zero-duration snaps are jarring on long lists; a brief signal of state change is helpful.
  • Closed content is aria-hidden. The wrapper sets aria-hidden={!isOpen} so screen readers don't announce collapsed bodies even though they stay in the DOM (necessary for the auto-height transition + for preserving descendant refs across open/close).
  • axe-core. Zero violations across the 4 × 7 variant × color matrix.

Animation

The open/close transition is pure CSS:

css
.content {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 240ms ease;
}
.content[data-state='open'] {
  grid-template-rows: 1fr;
}
.content > .inner {
  min-height: 0; /* lets the grid track shrink below intrinsic min-content */
}
.content {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 240ms ease;
}
.content[data-state='open'] {
  grid-template-rows: 1fr;
}
.content > .inner {
  min-height: 0; /* lets the grid track shrink below intrinsic min-content */
}

This is the modern "auto-height transition" pattern. It works on Chromium 117+, Safari 17+, and Firefox 121+ — collectively ~96% of users in 2026. Older browsers fall back to an instant snap (still functional, just no animation).

Theming

ts
defineTheme({
  components: {
    Accordion: {
      defaultProps: { variant: 'outline', size: 'sm', iconPosition: 'start' },
      styleOverrides: {
        root: '',
        item: 'border-2',
        trigger: 'rounded-md',
        content: '',
        contentInner: '',
        chevron: 'text-primary',
      },
    },
  },
});
defineTheme({
  components: {
    Accordion: {
      defaultProps: { variant: 'outline', size: 'sm', iconPosition: 'start' },
      styleOverrides: {
        root: '',
        item: 'border-2',
        trigger: 'rounded-md',
        content: '',
        contentInner: '',
        chevron: 'text-primary',
      },
    },
  },
});

Six slot keys: root, item, trigger, content, contentInner, chevron.

Props

PropTypeDefaultDescription
classNamestring—Merged via `tailwind-merge`. Last-wins.
collapsibleboolean—In single mode (`type="single"`), allow closing the currently-open item by clicking its trigger again. We default this to `true` — modern UX (FAQ pages, settings disclosure groups) expects to close the open section. Radix defaults to `false`; we differ deliberately.
colorResponsiveValue<AccordionColor>'neutral'Palette role driving hover tint / inline border / soft backgrounds.
defaultValuestring | string[]——
disabledboolean—Disable every item in the accordion. Per-item `disabled` overrides this.
iconPositionenum'end'Where the chevron sits relative to the label.
idstring—Identifier surfaced for testing / theme overrides; passes through to the wrapper `<div>`.
onValueChange((value: string) => void) | ((value: string[]) => void)——
sizeResponsiveValue<AccordionSize>'md'Visual size axis (trigger padding + content padding + font + chevron).
styleCSSProperties—Native CSS inline style. Useful for one-off measurements; prefer `sx` for theme tokens.
sxSx—Theme-aware inline style object.
typeenum——
valuestring | string[]——
variantResponsiveValue<AccordionVariant>'solid'Stylistic family.