PixelAccordion

Vertical list of expandable disclosure items, each header wired to its panel with aria-expanded / aria-controls.

  • Single-open by default; opt into multi-open via allowMultiple
  • First item auto-expanded unless collapsedByDefault is set
  • Surface-aware typography and borders (pixel vs linear)
  • aria-expanded + aria-controls wired per header for assistive tech
  • SSR-safe and tree-shakable; unopened panels are not rendered
Status
stable
Category
navigation
Since
v1.0.0

API

import { PixelAccordion } from '@pxlkit/ui-kit';
PropTypeDefaultDescription
items (required)AccordionItem[]—Accordion items rendered in order. First item is expanded by default.
allowMultiplebooleanfalseWhen true, multiple items can be expanded simultaneously.
collapsedByDefaultbooleanfalseWhen set, no item is initially expanded. Defaults to expanding the first.
surface'pixel' | 'linear'—Visual surface treatment override.

ref points to <div>.

Accessibility

WCAG target: 2.1 AA

  • disclosure

Each header is a native <button> with aria-expanded reflecting open state and aria-controls pointing at the panel id. Expanded panels are plain containers that follow their header in reading order — no role="region", which would proliferate landmarks (and collide when several accordions share titles), and so no name, which ARIA does not allow on an element without a role — matching the WAI-ARIA disclosure pattern stacked into an accordion. On the pixel surface a header shows keyboard focus inside its item, whose cut corners would clip a ring.

Keyboard

KeyActionWhen
EnterToggles the focused item open or closed—
SpaceToggles the focused item open or closed—
TabMoves focus between item headers—

Usage

import { PixelAccordion } from '@pxlkit/ui-kit';

const SAMPLE_ITEMS = [
  {
    id: 'overview',
    title: 'Overview',
    content: 'Introductory section that opens by default.',
  },
  {
    id: 'details',
    title: 'Details',
    content: 'Secondary section with extended copy.',
  },
  {
    id: 'faq',
    title: 'FAQ',
    content: 'Common questions and short answers.',
  },
];

export function Default() {
  return <PixelAccordion items={SAMPLE_ITEMS} />;
}

Examples

Default

import { PixelAccordion } from '@pxlkit/ui-kit';

const SAMPLE_ITEMS = [
  {
    id: 'overview',
    title: 'Overview',
    content: 'Introductory section that opens by default.',
  },
  {
    id: 'details',
    title: 'Details',
    content: 'Secondary section with extended copy.',
  },
  {
    id: 'faq',
    title: 'FAQ',
    content: 'Common questions and short answers.',
  },
];

export function Default() {
  return <PixelAccordion items={SAMPLE_ITEMS} />;
}

Collapsed by default

import { PixelAccordion } from '@pxlkit/ui-kit';

const SAMPLE_ITEMS = [
  {
    id: 'overview',
    title: 'Overview',
    content: 'Introductory section that opens by default.',
  },
  {
    id: 'details',
    title: 'Details',
    content: 'Secondary section with extended copy.',
  },
  {
    id: 'faq',
    title: 'FAQ',
    content: 'Common questions and short answers.',
  },
];

export function CollapsedByDefault() {
  return <PixelAccordion items={SAMPLE_ITEMS} collapsedByDefault />;
}

Allow multiple

import { PixelAccordion } from '@pxlkit/ui-kit';

const SAMPLE_ITEMS = [
  {
    id: 'overview',
    title: 'Overview',
    content: 'Introductory section that opens by default.',
  },
  {
    id: 'details',
    title: 'Details',
    content: 'Secondary section with extended copy.',
  },
  {
    id: 'faq',
    title: 'FAQ',
    content: 'Common questions and short answers.',
  },
];

export function AllowMultiple() {
  return <PixelAccordion items={SAMPLE_ITEMS} allowMultiple />;
}

Surfaces

import { PixelAccordion } from '@pxlkit/ui-kit';

const SAMPLE_ITEMS = [
  {
    id: 'overview',
    title: 'Overview',
    content: 'Introductory section that opens by default.',
  },
  {
    id: 'details',
    title: 'Details',
    content: 'Secondary section with extended copy.',
  },
  {
    id: 'faq',
    title: 'FAQ',
    content: 'Common questions and short answers.',
  },
];

export function Surfaces() {
  return (
    <div className="flex flex-col gap-4">
      <PixelAccordion items={SAMPLE_ITEMS} surface="pixel" />
      <PixelAccordion items={SAMPLE_ITEMS} surface="linear" />
    </div>
  );
}

Rich content

import { PixelAccordion } from '@pxlkit/ui-kit';

export function RichContent() {
  return (
    <PixelAccordion
      items={[
        {
          id: 'changelog',
          title: 'Changelog v1.2.0',
          content: (
            <ul className="list-disc pl-4">
              <li>Added accordion keyboard wiring</li>
              <li>Surface-aware typography</li>
              <li>Optional multi-open behaviour</li>
            </ul>
          ),
        },
        {
          id: 'migration',
          title: 'Migration notes',
          content: (
            <p>
              Pass <code>collapsedByDefault</code> to keep every item closed on
              first render.
            </p>
          ),
        },
      ]}
    />
  );
}

Related