FairGarden Design

Collapsible

A Base UI Collapsible for one in-place disclosure, such as "Show 12 more" under a truncated list, led by the LTA disclosure glyph.

import {
  Collapsible,
  CollapsiblePanel,
  CollapsibleTrigger,
} from '@fairgarden-private/design/components/Collapsible'

The trigger is the disclosure glyph at the 16 px tier plus a label, and the whole trigger is the target. openLabel swaps the label while the panel is open ("Show less"). The panel opens with a clip reveal, instantly under reduced motion, and stays in the DOM, so it prints expanded while the trigger is hidden. DisclosureGlyph is exported for any other in-place disclosure; it reads its state from the Base UI trigger around it.

Show more

Show more

The arrows point outward while collapsed and inward while open, muted then in the text ink; they never rotate. A disabled trigger mutes label and glyph.

  • Bluestem Prairie
  • Heron Marsh
  • Oak Hollow
  • Cedar Bluff

No subdivision, no new structures, and no removal of native trees.

CollapsibleVariants.tsx
import {
  Collapsible,
  CollapsiblePanel,
  CollapsibleTrigger,
} from '@fairgarden-private/design/components/Collapsible'
import styles from './variants.module.css'

const preserves = ['Bluestem Prairie', 'Heron Marsh', 'Oak Hollow', 'Cedar Bluff']
const more = ['Fox Run', 'Willow Bend', 'Kettle Pond', 'Sandhill Flats']

/** "Show more" with a swapped open label, and a disabled trigger. */
export function CollapsibleVariants() {
  return (
    <div className={styles.stack}>
      <Collapsible>
        <ul className={styles.list}>
          {preserves.map((name) => (
            <li key={name}>{name}</li>
          ))}
        </ul>
        <CollapsiblePanel>
          <ul className={styles.list}>
            {more.map((name) => (
              <li key={name}>{name}</li>
            ))}
          </ul>
        </CollapsiblePanel>
        <CollapsibleTrigger openLabel="Show less">Show {more.length} more</CollapsibleTrigger>
      </Collapsible>

      <Collapsible defaultOpen>
        <CollapsibleTrigger>Deed restrictions</CollapsibleTrigger>
        <CollapsiblePanel>
          <p className={styles.copy}>No subdivision, no new structures, and no removal of native trees.</p>
        </CollapsiblePanel>
      </Collapsible>

      <Collapsible disabled>
        <CollapsibleTrigger>Survey records (unavailable)</CollapsibleTrigger>
        <CollapsiblePanel>
          <p className={styles.copy}>Not shown.</p>
        </CollapsiblePanel>
      </Collapsible>
    </div>
  )
}

Primary scale

Primary scale

Primary bronze, open.

CollapsibleColor.tsx
import {
  Collapsible,
  CollapsiblePanel,
  CollapsibleTrigger,
} from '@fairgarden-private/design/components/Collapsible'
import styles from './color.module.css'

/** `primary` drives the label, glyph and panel text. */
export function CollapsibleColor() {
  return (
    <div className={styles.row}>
      <Collapsible primary="plum">
        <CollapsibleTrigger openLabel="Hide details">Show details</CollapsibleTrigger>
        <CollapsiblePanel>
          <p className={styles.copy}>Primary plum.</p>
        </CollapsiblePanel>
      </Collapsible>
      <Collapsible primary="bronze" defaultOpen>
        <CollapsibleTrigger openLabel="Hide details">Show details</CollapsibleTrigger>
        <CollapsiblePanel>
          <p className={styles.copy}>Primary bronze, open.</p>
        </CollapsiblePanel>
      </Collapsible>
    </div>
  )
}

On grounds

On paper and forest

paper

forest

CollapsibleGrounds.tsx
import {
  Collapsible,
  CollapsiblePanel,
  CollapsibleTrigger,
} from '@fairgarden-private/design/components/Collapsible'
import { PresetGround } from '@/components/PresetGround'
import styles from './grounds.module.css'

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

/** The glyph steps from muted to the text ink on paper; on forest both states are the one ink. */
export function CollapsibleGrounds() {
  return (
    <div className={styles.row}>
      {presets.map((preset) => (
        <PresetGround key={preset} preset={preset} className={styles.face}>
          <p className={styles.name}>{preset}</p>
          <Collapsible>
            <CollapsibleTrigger openLabel="Show less">Show 3 more</CollapsibleTrigger>
            <CollapsiblePanel>
              <p className={styles.copy}>Fox Run, Willow Bend, Kettle Pond.</p>
            </CollapsiblePanel>
          </Collapsible>
        </PresetGround>
      ))}
    </div>
  )
}

API Reference

Collapsible

One in-place disclosure (§10.13): a CollapsibleTrigger (the disclosure glyph at the inline tier plus a label) and a CollapsiblePanel. Use it for “Show more” on truncated lists and optional detail; never hide content a task depends on. Every panel prints expanded and the trigger is hidden.

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

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

secondary
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| '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.

CollapsibleTrigger and CollapsiblePanel

The trigger: the disclosure glyph (16 px, chrome tier) then the label, e.g. “Show 12 more”. The whole trigger is the ≥ 44 px target; the glyph alone never is.

PropTypeDescription
openLabel
React.ReactNode | undefined

The label while the panel is open, e.g. “Show less”. Omitted, the children stay. Author it in sentence case.

The panel. It stays in the DOM while closed (hiddenUntilFound, default true), so find-in-page reaches it and print shows it expanded. It opens with a clip reveal at --ds-duration-disclosure, instant under reduced motion.

DisclosureGlyph

LTA’s expand/collapse glyph [D109]: two thin arrows pointing outward from a short center hairline when collapsed, inward when expanded (§6.10). It reads the state from the nearest Base UI trigger’s data-panel-open, so place it inside an Accordion or Collapsible trigger. The direction swaps instantly and never rotates; collapsed it is --role-muted, expanded --primary12. Decorative: the trigger carries the state (aria-expanded).

PropTypeDescription
size
'row' | 'chrome' | null | undefined

row (default): --size-px-4 (20 px) at --ds-stroke-1-5, for trigger rows (Accordion, FAQ). chrome: --size-px-3 (16 px) at --ds-stroke-1-25, for bars, drawer groups and an inline “Show more”.

Additional types

collapsible
CollapsiblePanelProps

Props for CollapsiblePanel: Base UI Collapsible Panel props.

type CollapsiblePanelProps = CollapsiblePanel.CollapsiblePanelProps
CollapsibleProps

Props for Collapsible: Base UI Collapsible Root props plus the color axes.

type CollapsibleProps = CollapsibleRootProps & {
  primary?:
    | 'ruby'
    | 'olive'
    | 'sage'
    | 'slate'
    | 'sand'
    | 'gray'
    | 'mauve'
    | 'brown'
    | 'bronze'
    | 'gold'
    | 'red'
    | 'crimson'
    | 'tomato'
    | 'pink'
    | 'plum'
    | 'indigo'
    | 'iris'
    | 'violet'
    | 'purple'
    | null;
  secondary?:
    | 'ruby'
    | 'olive'
    | 'sage'
    | 'slate'
    | 'sand'
    | 'gray'
    | 'mauve'
    | 'brown'
    | 'bronze'
    | 'gold'
    | 'red'
    | 'crimson'
    | 'tomato'
    | 'pink'
    | 'plum'
    | 'indigo'
    | 'iris'
    | 'violet'
    | 'purple'
    | 'amber'
    | 'blue'
    | 'cyan'
    | 'grass'
    | 'green'
    | 'jade'
    | 'lime'
    | 'mint'
    | 'orange'
    | 'sky'
    | 'teal'
    | 'yellow'
    | null;
}
CollapsibleTriggerProps

Props for CollapsibleTrigger: Base UI Collapsible Trigger props plus the open label.

type CollapsibleTriggerProps = CollapsibleTriggerProps & { openLabel?: React.ReactNode }
disclosureGlyph

The disclosure glyph’s two tiers (§10.13).

type disclosureGlyph = disclosureGlyph
disclosureGlyphHost

Add to an icon-only control whose only content is the disclosure glyph (the staircase or section-bar toggle): hover and press then swap the glyph to the next tier’s weight (§10.1 icon states) [D181]. Trigger rows (Accordion, FAQ, Collapsible) never take it: their glyph does not change on hover [D109].

type disclosureGlyphHost = string
DisclosureGlyphProps

Props for DisclosureGlyph: SVG props (without children) and the tier.

type DisclosureGlyphProps = {
  /**
   * `row` (default): `--size-px-4` (20 px) at `--ds-stroke-1-5`, for trigger
   * rows (Accordion, FAQ). `chrome`: `--size-px-3` (16 px) at
   * `--ds-stroke-1-25`, for bars, drawer groups and an inline "Show more".
   */
  size?: 'row' | 'chrome' | null;
}

Specification: DESIGN-SYSTEM.md §10.13 (accordion and collapsible) and §6.10 (the disclosure glyph).