FairGarden Design

Accordion

A Base UI Accordion for long reference content scanned by heading: ruled rows, each a heading that opens its panel in place.

import { Accordion, AccordionItem } from '@fairgarden-private/design/components/Accordion'

The glyph is LTA's disclosure glyph: two thin arrows that point outward while collapsed and inward while expanded, muted then in the text ink. It swaps direction and never rotates; chevrons are kept for popups. Several items may be open at once. Panels open with a clip reveal, instantly under reduced motion, and stay in the DOM so find-in-page reaches them and print shows every panel expanded. Never hide content a task depends on, and never convert an accordion into tabs.

Glyphs and the strong top

Glyphs and the strong top

glyph="disclosure" is the default. headed draws a --border-size-2 rule above the first item when the accordion follows a heading. glyph="plusminus" is the documented alternate for product UI; never mix the two on one page. A disabled item mutes its title and glyph, but prefer removing it.

Open dawn to dusk every day. Boardwalks close after heavy frost.

Trail notes

AccordionVariants.tsx
import { Accordion, AccordionItem } from '@fairgarden-private/design/components/Accordion'
import styles from './variants.module.css'

/**
 * The ruled accordion with the LTA disclosure glyph (default), the strong
 * top rule for an accordion that follows a heading, and the plus/minus
 * alternate.
 */
export function AccordionVariants() {
  return (
    <div className={styles.stack}>
      <Accordion defaultValue={['hours']}>
        <AccordionItem value="hours" title="Hours and seasons">
          <p className={styles.copy}>Open dawn to dusk every day. Boardwalks close after heavy frost.</p>
        </AccordionItem>
        <AccordionItem value="parking" title="Parking and transit">
          <p className={styles.copy}>Twelve spaces at the north lot; the 42 bus stops at the gate.</p>
        </AccordionItem>
        <AccordionItem value="rules" title="Preserve rules" disabled>
          <p className={styles.copy}>Unavailable while the page is updated.</p>
        </AccordionItem>
      </Accordion>

      <section className={styles.section}>
        <h3 className={styles.heading}>Trail notes</h3>
        <Accordion headed>
          <AccordionItem value="loop" title="Meadow loop, 1.2 miles">
            <p className={styles.copy}>Flat and accessible, with benches every quarter mile.</p>
          </AccordionItem>
          <AccordionItem value="ridge" title="Ridge trail, 3.4 miles">
            <p className={styles.copy}>Steep switchbacks; muddy in spring.</p>
          </AccordionItem>
        </Accordion>
      </section>

      <Accordion glyph="plusminus">
        <AccordionItem value="account" title="Account settings">
          <p className={styles.copy}>The plus/minus circle is the alternate for product UI.</p>
        </AccordionItem>
        <AccordionItem value="notifications" title="Notifications">
          <p className={styles.copy}>Never mix it with the disclosure glyph on one page.</p>
        </AccordionItem>
      </Accordion>
    </div>
  )
}

Primary scale

Primary scale

primary drives every part. The expanded glyph is the text ink, not an accent, so secondary is unused.

Rules, titles, glyph and panel text re-resolve together.

AccordionColor.tsx
import { Accordion, AccordionItem } from '@fairgarden-private/design/components/Accordion'
import styles from './color.module.css'

/** `primary` drives every part: rules, titles, glyph and panel text. `secondary` is unused. */
export function AccordionColor() {
  return (
    <div className={styles.stack}>
      <Accordion primary="plum" defaultValue={['a']}>
        <AccordionItem value="a" title="Primary plum">
          <p className={styles.copy}>Rules, titles, glyph and panel text re-resolve together.</p>
        </AccordionItem>
        <AccordionItem value="b" title="Collapsed glyph">
          <p className={styles.copy}>The collapsed glyph is the muted step of the same scale.</p>
        </AccordionItem>
      </Accordion>
      <Accordion primary="slate" headed>
        <AccordionItem value="a" title="Primary slate, headed">
          <p className={styles.copy}>The strong top rule is the text step of the scale.</p>
        </AccordionItem>
      </Accordion>
    </div>
  )
}

On grounds

On paper and forest

paper

Arrows point inward.

forest

Arrows point inward.

AccordionGrounds.tsx
import { Accordion, AccordionItem } from '@fairgarden-private/design/components/Accordion'
import { PresetGround } from '@/components/PresetGround'
import styles from './grounds.module.css'

const presets = ['paper', 'forest'] as const

/** Pure type and line: on forest the collapsed glyph is already the one ink, so direction carries the state. */
export function AccordionGrounds() {
  return (
    <div className={styles.row}>
      {presets.map((preset) => (
        <PresetGround key={preset} preset={preset} className={styles.face}>
          <p className={styles.name}>{preset}</p>
          <Accordion defaultValue={['open']}>
            <AccordionItem value="open" title="Expanded">
              <p className={styles.copy}>Arrows point inward.</p>
            </AccordionItem>
            <AccordionItem value="closed" title="Collapsed">
              <p className={styles.copy}>Arrows point outward.</p>
            </AccordionItem>
          </Accordion>
        </PresetGround>
      ))}
    </div>
  )
}

API Reference

Accordion

A set of disclosure rows divided by --role-rule rules, including after the last item. Several items may be open at once (multiple, default true). Closed panels stay in the DOM (hiddenUntilFound, default true), so find-in-page reaches them and every panel prints expanded. Never convert an accordion into tabs.

PropTypeDescription
glyph
'disclosure' | 'plusminus' | null | undefined

disclosure (default): the LTA expand/collapse glyph at the row’s end [D109]. plusminus: the outline circle holding + or −, the documented alternate for product UI. Never mix the two on one page.

headed
boolean | null | undefined

Draws a --border-size-2 --primary12 rule above the first item. Use it when the accordion directly follows a heading. Default false.

primary
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'ruby'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null
| undefined

Primary Radix scale: rules, titles, glyph and panel text. Never defaulted; omitted, it inherits the scope [D133].

secondary
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'ruby'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null
| undefined

Secondary Radix scale, accepted for the shared contract; no part uses it.

AccordionItem

One row: a heading wrapping the trigger (optional icon, title, glyph at the row’s end, top-aligned to the title’s first line) and the panel. The whole row is the target. There is no fill change when it opens.

PropTypeDescription
headingLevel
HeadingLevel | undefined

The heading level wrapping the trigger. Default 3.

icon
| 'menu'
| 'search'
| 'circle'
| 'arrow_forward'
| 'arrow_upward'
| 'expand_more'
| 'close'
| 'remove'
| 'add'
| 'check'
| 'chevron_right'
| 'chevron_left'
| 'more_horiz'
| 'play_arrow'
| 'pause'
| 'download'
| 'zoom_in'
| 'zoom_out'
| 'recenter'
| 'help'
| 'mail'
| undefined

An optional leading subject symbol (§6.10), Material Symbols Rounded at the tag tier; the panel indents to the title column. Dropped below 360 px so the title keeps its width.

panelProps
Omit<AccordionPanelProps, 'children'> | undefined

Props for the panel, such as keepMounted.

title
React.ReactNode | undefined

The row’s title, type-subhead in --primary12. Write it to stand alone: it becomes the printed heading.

Additional types

accordion
AccordionGlyph
type AccordionGlyph = 'disclosure' | 'plusminus'<'disclosure' | 'plusminus' | null | undefined>
AccordionItemProps

Props for AccordionItem: Base UI Accordion Item props (without title) plus the row content.

type AccordionItemProps = {
  /**
   * The row's title, `type-subhead` in `--primary12`. Write it to stand
   * alone: it becomes the printed heading.
   */
  title: React.ReactNode;
  /**
   * An optional leading subject symbol (§6.10), Material Symbols Rounded at
   * the tag tier; the panel indents to the title column. Dropped below
   * 360 px so the title keeps its width.
   */
  icon?:
    | 'menu'
    | 'search'
    | 'circle'
    | 'arrow_forward'
    | 'arrow_upward'
    | 'expand_more'
    | 'close'
    | 'remove'
    | 'add'
    | 'check'
    | 'chevron_right'
    | 'chevron_left'
    | 'more_horiz'
    | 'play_arrow'
    | 'pause'
    | 'download'
    | 'zoom_in'
    | 'zoom_out'
    | 'recenter'
    | 'help'
    | 'mail';
  /** The heading level wrapping the trigger. Default `3`. */
  headingLevel?: HeadingLevel;
  /** Props for the panel, such as `keepMounted`. */
  panelProps?: Omit<AccordionPanelProps, 'children'>;
}
AccordionProps

Props for Accordion: Base UI Accordion Root props plus the glyph, headed and color axes.

type AccordionProps<Value = unknown> = AccordionRoot.Props<Value> & {
  glyph?: 'disclosure' | 'plusminus' | null;
  headed?: boolean | null;
  primary?:
    | 'olive'
    | 'sage'
    | 'slate'
    | 'sand'
    | 'gray'
    | 'mauve'
    | 'brown'
    | 'bronze'
    | 'gold'
    | 'red'
    | 'ruby'
    | 'crimson'
    | 'tomato'
    | 'pink'
    | 'plum'
    | 'indigo'
    | 'iris'
    | 'violet'
    | 'purple'
    | null;
  secondary?:
    | 'olive'
    | 'sage'
    | 'slate'
    | 'sand'
    | 'gray'
    | 'mauve'
    | 'brown'
    | 'bronze'
    | 'gold'
    | 'red'
    | 'ruby'
    | 'crimson'
    | 'tomato'
    | 'pink'
    | 'plum'
    | 'indigo'
    | 'iris'
    | 'violet'
    | 'purple'
    | 'amber'
    | 'blue'
    | 'cyan'
    | 'grass'
    | 'green'
    | 'jade'
    | 'lime'
    | 'mint'
    | 'orange'
    | 'sky'
    | 'teal'
    | 'yellow'
    | null;
}

Specification: DESIGN-SYSTEM.md §10.13 (accordion and collapsible).