Skip to content
LuoForge/Tungsten
Components/Accordion·

Accordion

since v0.2.0

Collapsible content container — use for FAQs and progressive disclosure sections.

Supports both uncontrolled (defaultValue) and controlled (value + onValueChange) modes.

Install

Add the package and import the component.

pnpm add @hey-mike/tungsten
pnpm add @hey-mike/tungsten
import { Accordion } from '@hey-mike/tungsten';
import { Accordion } from '@hey-mike/tungsten';

Preview

Same fixtures used by the visual-regression suite.

Usage

apps/docs/app/snapshots/accordion/page.tsx

import {
  Accordion,
  AccordionItem,
  AccordionTrigger,
  AccordionPanel,
} from '@hey-mike/tungsten';
import { VariantGrid } from '../_components/VariantGrid';
import { HeroSpecimen } from '../_components/HeroSpecimen';

const items = [
  { value: 'a', label: 'Section A', body: 'Body of section A.' },
  { value: 'b', label: 'Section B (open)', body: 'Body of section B — visible because defaultValue is "b".' },
  { value: 'c', label: 'Section C', body: 'Body of section C.' },
];

export default function AccordionSnapshot() {
  return (
    <VariantGrid
      title="Accordion"
      hero={
        <HeroSpecimen>
          <Accordion defaultValue="b">
            <AccordionItem value="a">
              <AccordionTrigger>Section A</AccordionTrigger>
              <AccordionPanel className="text-ink-2 pb-3 text-sm">Body of section A.</AccordionPanel>
            </AccordionItem>
            <AccordionItem value="b">
              <AccordionTrigger>Section B</AccordionTrigger>
              <AccordionPanel className="text-ink-2 pb-3 text-sm">Body of section B.</AccordionPanel>
            </AccordionItem>
          </Accordion>
        </HeroSpecimen>
      }
      variants={[
        {
          label: 'one open',
          node: (
            <Accordion defaultValue="b" className="w-[480px]">
              {items.map((item) => (
                <AccordionItem key={item.value} value={item.value}>
                  <AccordionTrigger>{item.label}</AccordionTrigger>
                  <AccordionPanel className="pb-4 text-sm leading-relaxed text-ink-2">
                    {item.body}
                  </AccordionPanel>
                </AccordionItem>
              ))}
            </Accordion>
          ),
        },
      ]}
    />
  );
}
import {
  Accordion,
  AccordionItem,
  AccordionTrigger,
  AccordionPanel,
} from '@hey-mike/tungsten';
import { VariantGrid } from '../_components/VariantGrid';
import { HeroSpecimen } from '../_components/HeroSpecimen';

const items = [
  { value: 'a', label: 'Section A', body: 'Body of section A.' },
  { value: 'b', label: 'Section B (open)', body: 'Body of section B — visible because defaultValue is "b".' },
  { value: 'c', label: 'Section C', body: 'Body of section C.' },
];

export default function AccordionSnapshot() {
  return (
    <VariantGrid
      title="Accordion"
      hero={
        <HeroSpecimen>
          <Accordion defaultValue="b">
            <AccordionItem value="a">
              <AccordionTrigger>Section A</AccordionTrigger>
              <AccordionPanel className="text-ink-2 pb-3 text-sm">Body of section A.</AccordionPanel>
            </AccordionItem>
            <AccordionItem value="b">
              <AccordionTrigger>Section B</AccordionTrigger>
              <AccordionPanel className="text-ink-2 pb-3 text-sm">Body of section B.</AccordionPanel>
            </AccordionItem>
          </Accordion>
        </HeroSpecimen>
      }
      variants={[
        {
          label: 'one open',
          node: (
            <Accordion defaultValue="b" className="w-[480px]">
              {items.map((item) => (
                <AccordionItem key={item.value} value={item.value}>
                  <AccordionTrigger>{item.label}</AccordionTrigger>
                  <AccordionPanel className="pb-4 text-sm leading-relaxed text-ink-2">
                    {item.body}
                  </AccordionPanel>
                </AccordionItem>
              ))}
            </Accordion>
          ),
        },
      ]}
    />
  );
}

Props

Surface specific to <Accordion />.

PropTypeDefaultDescription
type"single" | "multiple"
defaultValuestring | string[]Initial open section key. Uncontrolled mode. Initial open section keys. Uncontrolled mode.
valuestring | string[] | nullControlled open section key. Pass null for "nothing open". Controlled open section keys. Pass [] for "nothing open".
onValueChange((value: string | null) => void) | ((value: string | null) => void) | ((value: string[]) => void) | ((value: string[]) => void)Optional in uncontrolled mode — fires on every toggle for observation. Required in controlled mode. Fires with the newly open key, or null when collapsed. Optional in uncontrolled mode — fires with the next open-key array. Required in controlled mode. Fires with the next open-key array.

Sub-components

Composition slots re-exported from the same module.

AccordionItem

PropTypeDefaultDescription
value*stringUnique key for this section. Matches defaultValue or value on the root Accordion.

AccordionGroup

PropTypeDefaultDescription
type"single" | "multiple"
defaultValuestring | string[]Initial open section key. Uncontrolled mode. Initial open section keys. Uncontrolled mode.
valuestring | string[] | nullControlled open section key. Pass null for "nothing open". Controlled open section keys. Pass [] for "nothing open".
onValueChange((value: string | null) => void) | ((value: string | null) => void) | ((value: string[]) => void) | ((value: string[]) => void)Optional in uncontrolled mode — fires on every toggle for observation. Required in controlled mode. Fires with the newly open key, or null when collapsed. Optional in uncontrolled mode — fires with the next open-key array. Required in controlled mode. Fires with the next open-key array.

AccordionTrigger

PropTypeDefaultDescription
iconReactElement<unknown, string | JSXElementConstructor<any>>Optional leading icon. Pass a ReactElement (e.g. a lucide-react icon).
showChevronbooleanShow the built-in chevron. Defaults to true. Pass false to suppress.

Source