Menu

Site and documentation links

Search documentation

Type to filter pages and sections. Use the arrow keys to move and Enter to open.

    Accordion

    A stack of headings that each show or hide a section of content.

    Yes. It follows the WAI-ARIA Accordion pattern, and the arrow keys move between headers.

    View code
    import {
      Accordion,
      AccordionHeader,
      AccordionItem,
      AccordionPanel,
      AccordionTrigger
    } from '@syncclouds/ui'
    
    const QUESTIONS = [
      {
        value: 'accessible',
        question: 'Is it accessible?',
        answer:
          'Yes. It follows the WAI-ARIA Accordion pattern, and the arrow keys move between headers.'
      },
      {
        value: 'styled',
        question: 'Is it styled?',
        answer:
          'Only if you import the optional theme. Without it, the parts render plain elements you style yourself.'
      },
      {
        value: 'multiple',
        question: 'Can more than one item open?',
        answer: 'Yes. Set type to multiple and each item opens on its own.'
      }
    ]
    
    export function AccordionDemo() {
      return (
        <Accordion defaultValue={['accessible']} className="w-full max-w-md">
          {QUESTIONS.map(item => (
            <AccordionItem key={item.value} value={item.value}>
              <AccordionHeader>
                <AccordionTrigger>{item.question}</AccordionTrigger>
              </AccordionHeader>
              <AccordionPanel>{item.answer}</AccordionPanel>
            </AccordionItem>
          ))}
        </Accordion>
      )
    }

    Installation

    npm install @syncclouds/ui

    Usage

    import {
      Accordion,
      AccordionHeader,
      AccordionItem,
      AccordionPanel,
      AccordionTrigger
    } from '@syncclouds/ui'
    <Accordion defaultValue={['item-1']}>
      <AccordionItem value="item-1">
        <AccordionHeader>
          <AccordionTrigger>Is it accessible?</AccordionTrigger>
        </AccordionHeader>
        <AccordionPanel>Yes. It follows the WAI-ARIA Accordion pattern.</AccordionPanel>
      </AccordionItem>
    </Accordion>

    Anatomy

    Accordion
    └── AccordionItem
        ├── AccordionHeader
        │   └── AccordionTrigger
        └── AccordionPanel

    Multiple

    Set type="multiple" so each item opens and closes on its own.

    Choose which emails and push alerts you receive.

    Decide who can see your profile and activity.

    View code
    import {
      Accordion,
      AccordionHeader,
      AccordionItem,
      AccordionPanel,
      AccordionTrigger
    } from '@syncclouds/ui'
    
    const SETTINGS = [
      { value: 'notifications', title: 'Notifications', body: 'Choose which emails and push alerts you receive.' },
      { value: 'privacy', title: 'Privacy', body: 'Decide who can see your profile and activity.' },
      { value: 'billing', title: 'Billing', body: 'Update your payment method and download invoices.' }
    ]
    
    export function AccordionMultiple() {
      return (
        <Accordion type="multiple" defaultValue={['notifications', 'privacy']} className="w-full max-w-md">
          {SETTINGS.map(item => (
            <AccordionItem key={item.value} value={item.value}>
              <AccordionHeader>
                <AccordionTrigger>{item.title}</AccordionTrigger>
              </AccordionHeader>
              <AccordionPanel>{item.body}</AccordionPanel>
            </AccordionItem>
          ))}
        </Accordion>
      )
    }

    Disabled item

    A disabled item keeps its place but its trigger does not respond.

    View code
    import {
      Accordion,
      AccordionHeader,
      AccordionItem,
      AccordionPanel,
      AccordionTrigger
    } from '@syncclouds/ui'
    
    export function AccordionDisabled() {
      return (
        <Accordion className="w-full max-w-md">
          <AccordionItem value="history">
            <AccordionHeader>
              <AccordionTrigger>Account history</AccordionTrigger>
            </AccordionHeader>
            <AccordionPanel>Every sign-in from the last 90 days, with device and location.</AccordionPanel>
          </AccordionItem>
          <AccordionItem value="exports" disabled>
            <AccordionHeader>
              <AccordionTrigger>Data exports (on paid plans)</AccordionTrigger>
            </AccordionHeader>
            <AccordionPanel>Download everything in your workspace as CSV.</AccordionPanel>
          </AccordionItem>
          <AccordionItem value="email">
            <AccordionHeader>
              <AccordionTrigger>Email address</AccordionTrigger>
            </AccordionHeader>
            <AccordionPanel>Change it here; we send a link to confirm the new address.</AccordionPanel>
          </AccordionItem>
        </Accordion>
      )
    }

    Heading level

    AccordionHeader renders an h3. Use asChild to keep the page outline in order.

    View code
    import {
      Accordion,
      AccordionHeader,
      AccordionItem,
      AccordionPanel,
      AccordionTrigger
    } from '@syncclouds/ui'
    
    /* The page above this accordion already has an h2 and h3, so each header
       renders an h4 to keep the outline in order. */
    export function AccordionHeadingLevel() {
      return (
        <Accordion className="w-full max-w-md">
          {['Monday', 'Tuesday'].map(day => (
            <AccordionItem key={day} value={day}>
              <AccordionHeader asChild>
                <h4>
                  <AccordionTrigger>{day}</AccordionTrigger>
                </h4>
              </AccordionHeader>
              <AccordionPanel>Open from 9:00 to 17:00.</AccordionPanel>
            </AccordionItem>
          ))}
        </Accordion>
      )
    }

    Without the theme

    The same parts with only Tailwind CSS classes on their data attributes.

    The component brings behaviour and accessibility. You bring every style.

    View code
    import {
      Accordion,
      AccordionHeader,
      AccordionItem,
      AccordionPanel,
      AccordionTrigger
    } from '@syncclouds/ui'
    
    const ITEMS = [
      { value: 'one', title: 'What does headless mean?', body: 'The component brings behaviour and accessibility. You bring every style.' },
      { value: 'two', title: 'How do I style the open state?', body: 'Target data-state="open", as the classes on this example do.' }
    ]
    
    export function AccordionUnstyled() {
      return (
        <Accordion defaultValue={['one']} className="w-full max-w-md divide-y divide-zinc-200 dark:divide-zinc-700">
          {ITEMS.map(item => (
            <AccordionItem key={item.value} value={item.value} className="py-1">
              <AccordionHeader className="m-0">
                <AccordionTrigger className="w-full rounded-md py-3 text-left font-medium text-zinc-900 outline-offset-2 focus-visible:outline-2 focus-visible:outline-indigo-600 data-[state=open]:text-indigo-700 dark:text-zinc-100 dark:data-[state=open]:text-indigo-300">
                  {item.title}
                </AccordionTrigger>
              </AccordionHeader>
              <AccordionPanel className="pb-3 text-zinc-600 dark:text-zinc-400">{item.body}</AccordionPanel>
            </AccordionItem>
          ))}
        </Accordion>
      )
    }

    Accessibility

    Follows the WAI-ARIA Accordion pattern.

    KeysWhat they do
    Enter, SpaceOpens or closes the focused item.
    TabMoves to the next focusable element, including links inside open panels.
    Arrow Down, Arrow UpMoves to the next or previous header, wrapping at the ends.
    Home, EndMoves to the first or last header.
    • Each panel is a region labelled by its trigger, so screen readers announce which section they are in.
    • Closed panels use the hidden attribute, so their content is out of the tab order.

    API reference

    Every part also accepts the props of the element it renders, including className and ref.

    Accordionrenders <div>

    Holds the items and which of them are open.

    PropTypeDefaultDescription
    type"single" | "multiple""single"single: opening one item closes the others. multiple: items open on their own.
    valuestring[]noneThe open items, when you control them.
    defaultValuestring[][]The items open at first, when the accordion controls itself.
    onValueChange(value: string[]) => voidnoneCalled with the open items after every change.
    Data attributeValues
    data-scope"accordion"
    data-part"root"

    AccordionItemrenders <div>

    One heading and its panel.

    PropTypeDefaultDescription
    valuestringnoneRequired. Identifies the item in value and defaultValue.
    disabledbooleanfalseStops the item from opening or closing.
    Data attributeValues
    data-state"open" | "closed"
    data-disabledpresent when disabled

    AccordionHeaderrenders <h3>

    The heading around the trigger. Use asChild to render another heading level.

    PropTypeDefaultDescription
    asChildbooleanfalseRender your own heading element instead of an h3.
    Data attributeValues
    data-state"open" | "closed"

    AccordionTriggerrenders <button>

    Shows or hides the panel. Carries aria-expanded and aria-controls.

    Data attributeValues
    data-state"open" | "closed"
    data-disabledpresent when disabled

    AccordionPanelrenders <div>

    The content. A region named by its trigger, hidden while closed.

    Data attributeValues
    data-state"open" | "closed"
    Built by Syncclouds. Syncclouds UI is in development.