RoadieRoadie
  • Home
  • Foundations
  • Tokens
  • Components
  • Charts
  • Widgets
  • Appearance
HomeFoundationsComponents
Appearance

Components

  • Navigation

    • Breadcrumb
    • Navigator
    • Steps
    • Tabs
  • Layout

    • Pane
    • ScrollArea
    • Separator
  • Actions

    • Button
    • IconButton
    • Toggle
    • Toggle group
  • Forms

    • Forms overview
    • Autocomplete
    • Checkbox
    • Combobox
    • Field
    • Fieldset
    • Input
    • Label
    • Number field
    • OTP field
    • Radio group
    • Select
    • Slider
    • Switch
    • Textarea
  • Overlays

    • Dialog
    • Drawer
    • Menu
    • Popover
    • Toast
    • Tooltip
  • Collections

    • Accordion
    • Card
    • Carousel
    • Collapsible
    • DataTable
    • List
    • Marquee
    • Table
  • Status

    • Badge
    • Callout
    • Countdown
    • Empty state
    • Meter
    • Progress
    • Skeleton
    • Sparkline
    • StatTile
  • Media & brand

    • Avatar
    • Icon Tile
    • Image
    • Logo
    • QR code
    • SpotIllustration
  • Text

    • CalendarTile
    • Code
    • DateTime
    • Duration
    • Highlight
    • Mark
    • Prose

Collapsible

A single panel that a trigger shows and hides.

Import

import { Collapsible } from '@oztix/roadie-components/collapsible'

Examples

Default

A bare trigger is a text control with a trailing caret. Pad the content inside the panel, not the panel itself, so it closes all the way.

The part is .Panel, matching Base UI and Tabs.Panel. Roadie keeps .Content for popup wrappers such as Select.Content.

<div className='grid gap-3'>
  <ul className='grid gap-2'>
    <li>General admission</li>
    <li>Concession</li>
    <li>VIP mezzanine</li>
  </ul>
  <Collapsible>
    <Collapsible.Trigger>Show all 12 ticket types</Collapsible.Trigger>
    <Collapsible.Panel>
      <ul className='grid gap-2 pt-3'>
        <li>Early bird</li>
        <li>Student</li>
        <li>Seniors card</li>
        <li>Group of four</li>
        <li>Family pass</li>
        <li>Companion card</li>
        <li>Standing, front section</li>
        <li>Seated, balcony</li>
        <li>Meet and greet</li>
      </ul>
    </Collapsible.Panel>
  </Collapsible>
</div>

States

<div className='grid gap-6'>
  <div className='grid justify-items-start gap-1'>
    <p className='text-sm text-subtle'>Closed</p>
    <Collapsible>
      <Collapsible.Trigger>Show all 12 ticket types</Collapsible.Trigger>
      <Collapsible.Panel><p className='pt-2'>Early bird, student and nine more.</p></Collapsible.Panel>
    </Collapsible>
  </div>
  <div className='grid justify-items-start gap-1'>
    <p className='text-sm text-subtle'>Open</p>
    <Collapsible defaultOpen>
      <Collapsible.Trigger>Show all 12 ticket types</Collapsible.Trigger>
      <Collapsible.Panel><p className='pt-2'>Early bird, student and nine more.</p></Collapsible.Panel>
    </Collapsible>
  </div>
  <div className='grid justify-items-start gap-1'>
    <p className='text-sm text-subtle'>Disabled</p>
    <Collapsible disabled>
      <Collapsible.Trigger>Show all 12 ticket types</Collapsible.Trigger>
      <Collapsible.Panel><p className='pt-2'>Early bird, student and nine more.</p></Collapsible.Panel>
    </Collapsible>
  </div>
  <div className='grid justify-items-start gap-1'>
    <p className='text-sm text-subtle'>Without a caret</p>
    <Collapsible>
      <Collapsible.Trigger showCaret={false}>Show all 12 ticket types</Collapsible.Trigger>
      <Collapsible.Panel><p className='pt-2'>Early bird, student and nine more.</p></Collapsible.Panel>
    </Collapsible>
  </div>
</div>

With a button

Render the trigger onto a Button. The trigger keeps the button's styles and adds its own caret. Here open is controlled so the label can change, and hiddenUntilFound lets find in page reach the closed text.

function SetTimes() {
  const [open, setOpen] = useState(false)
  return (
    <Collapsible open={open} onOpenChange={setOpen} className='grid max-w-prose justify-items-start gap-2'>
      <p>The Velvet Static bring their third record to the Paper Lantern Hall in Fitzroy for one night only, with support from Harbour Moth.</p>
      <Collapsible.Panel hiddenUntilFound>
        <ul className='grid gap-1'>
          <li>7pm: doors</li>
          <li>8pm: Harbour Moth</li>
          <li>9pm: The Velvet Static</li>
        </ul>
      </Collapsible.Panel>
      <Collapsible.Trigger render={<Button emphasis='subtler' size='sm' />}>
        {open ? 'Hide set times' : 'Show set times'}
      </Collapsible.Trigger>
    </Collapsible>
  )
}

render(<SetTimes />)

Filters

Tuck advanced filters behind a button. keepMounted keeps the closed panel in the page, so what someone typed survives closing it.

<Collapsible className='grid gap-3'>
  <div className='flex flex-wrap items-center gap-2'>
    <Field className='min-w-48 flex-1'>
      <Field.Label className='sr-only'>Search events</Field.Label>
      <Field.Input placeholder='Search events' />
    </Field>
    <Collapsible.Trigger render={<Button />}>
      <SlidersHorizontalIcon weight='bold' className='size-4' />
      Advanced filters
    </Collapsible.Trigger>
  </div>
  <Collapsible.Panel keepMounted>
    <div className='grid gap-4 rounded-xl emphasis-sunken p-4 sm:grid-cols-2'>
      <Field>
        <Field.Label>Suburb</Field.Label>
        <Field.Input placeholder='Newtown' />
      </Field>
      <Field>
        <Field.Label>Genre</Field.Label>
        <Select>
          <Select.Trigger>
            <Select.Value placeholder='Any genre' />
            <Select.Icon />
          </Select.Trigger>
          <Select.Content>
            <Select.Item value='rock'>Rock</Select.Item>
            <Select.Item value='electronic'>Electronic</Select.Item>
            <Select.Item value='comedy'>Comedy</Select.Item>
          </Select.Content>
        </Select>
      </Field>
    </div>
  </Collapsible.Panel>
</Collapsible>

Clamped text

Put Collapsible.Text inside a Collapsible to clamp a paragraph to a few lines and end the last one with an inline "…more". The Collapsible holds the open state, so open, defaultOpen and onOpenChange go on it, as they do for a panel. It fades the end of that line into whatever surface it sits on, and shows the trigger only when the text runs past the clamp. lines sets the clamp (3 by default), moreLabel and lessLabel set the labels, and lessLabel={null} expands only.

<ul className='grid max-w-md gap-3'>
  <li className='grid gap-1 rounded-xl emphasis-raised p-4'>
    <div className='flex items-baseline justify-between gap-4'>
      <p className='font-medium text-strong'>General admission</p>
      <p className='text-strong'>$59.90</p>
    </div>
    <Collapsible>
      <Collapsible.Text lines={2} className='text-sm text-subtle'>
        Standing entry to Harbour Moth at the Paper Lantern Hall in Fitzroy. Doors open at 7pm and the band starts at 9pm. The venue is all ages until 10pm, and under 18s must stay with a parent or guardian. Re-entry is not allowed once you leave the venue.
      </Collapsible.Text>
    </Collapsible>
  </li>
  <li className='grid gap-1 rounded-xl emphasis-raised p-4'>
    <div className='flex items-baseline justify-between gap-4'>
      <p className='font-medium text-strong'>Mezzanine seat</p>
      <p className='text-strong'>$89.90</p>
    </div>
    <Collapsible>
      <Collapsible.Text lines={2} className='text-sm text-subtle'>
        A reserved seat on the upstairs mezzanine, with its own bar.
      </Collapsible.Text>
    </Collapsible>
  </li>
</ul>

Guidelines

Collapsible or Accordion

<Collapsible>
<Collapsible.Trigger>Show all 12 ticket types</Collapsible.Trigger>
<Collapsible.Panel>…</Collapsible.Panel>
</Collapsible>

Do

Use Collapsible for one panel that belongs to the content around it: the rest of a list, the rest of a description, extra filters.

<Collapsible>…</Collapsible>
<Collapsible>…</Collapsible>
<Collapsible>…</Collapsible>

Don’t

Don't stack Collapsibles into a list of questions or sections. Use Accordion, which groups them and can keep one open at a time.

Say what opens

Do

Name what the panel holds, with a count where you have one: "Show all 12 ticket types", "Advanced filters".

Don’t

Don't hide what most people need, like the price or the date. Keep it above the trigger.

Clamp only what people can skip

<Collapsible>
<Collapsible.Text lines={2}>{ticket.description}</Collapsible.Text>
</Collapsible>

Do

Clamp long descriptions that people skim, such as the details of a ticket type.

Don’t

Don't clamp what people must read before they buy: terms, prices, fees or entry conditions. Show it in full.

Accessibility

  • The trigger is a button with aria-expanded and aria-controls pointing at the panel. Enter or Space toggles it.
  • The caret is decorative and hidden from assistive technology. The trigger's text is its name.
  • A closed panel leaves the page unless you pass keepMounted or hiddenUntilFound. With hiddenUntilFound, find in page can match the closed text and opens the panel to show it.
  • Collapsible.Text keeps the whole text in the page, so screen readers and find in page reach all of it. Its trigger is a button with aria-expanded and aria-controls pointing at the text, and focus stays on it as it toggles. The ellipsis is decorative. With lessLabel={null} the trigger goes once the text expands, and focus moves to the text.
  • The height motion stops under reduced motion.

API reference

Collapsible

Base UI
openboolean

Whether the collapsible panel is currently open. To render an uncontrolled collapsible, use the `defaultOpen` prop instead.

defaultOpenboolean

Whether the collapsible panel is initially open. To render a controlled collapsible, use the `open` prop instead.

Defaults to false.

onOpenChange((open: boolean, eventDetails: CollapsibleRootChangeEventDetails) => void)

Event handler called when the panel is opened or closed.

disabledboolean

Whether the component should ignore user interaction.

Defaults to false.

classNamestring | ((state: CollapsibleRootState) => string)

CSS class applied to the element, or a function that returns a class based on the component's state.

Collapsible.Panel

Base UI
classNamestring | ((state: CollapsiblePanelState) => string)

CSS class applied to the element, or a function that returns a class based on the component's state.

Inherited from CollapsiblePanelProps

hiddenUntilFoundboolean

Allows the browser's built-in page search to find and expand the panel contents. Overrides the `keepMounted` prop and uses `hidden="until-found"` to hide the element without removing it from the DOM.

Defaults to false.

keepMountedboolean

Whether to keep the element in the DOM while the panel is hidden. This prop is ignored when `hiddenUntilFound` is used.

Defaults to false.

Collapsible.Text

linesnumber

How many lines show while closed.

Defaults to 3.

moreLabelReactNode

The closed trigger's label, shown after a decorative ellipsis.

Defaults to more.

lessLabelReactNode

The open trigger's label. `null` expands only, with no way back.

Defaults to Show less.

renderRoadieRenderProp

Swap the paragraph for another element, such as `<div />`.

Collapsible.Trigger

Base UI
classNamestring | ((state: CollapsibleTriggerState) => string)

CSS class applied to the element, or a function that returns a class based on the component's state.

showCaretboolean

Show a trailing caret that turns when the panel opens.

Defaults to true.

Inherited from NativeButtonProps

nativeButtonboolean

Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`).

Defaults to true.

Previous page← CarouselNext pageList →