gnome-ui
Components

Accordion

A set of collapsible panels with headings.

Accordion component

import * as React from 'react';
import { Accordion } from 'gnome-ui/accordion';

export default function ExampleAccordion() {
  return (
    <Accordion.Root
      multiple
      className="flex w-96 max-w-[calc(100vw-8rem)] flex-col justify-center text-gray-900"
    >
          <Accordion.Item className="border-b border-gray-200">
            <Accordion.Header>
              <Accordion.Trigger className="group relative flex w-full items-baseline justify-between gap-4 bg-gray-50 py-2 pr-1 pl-3 text-left font-medium hover:bg-gray-100 focus-visible:z-1 focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-800">
            What is Base UI?
            <PlusIcon className="mr-2 size-3 shrink-0 transition-all ease-out group-data-[panel-open]:scale-110 group-data-[panel-open]:rotate-45" />
          </Accordion.Trigger>
        </Accordion.Header>
            <Accordion.Panel className="h-[var(--accordion-panel-height)] overflow-hidden text-base text-gray-600 transition-[height] ease-out data-[ending-style]:h-0 data-[starting-style]:h-0">
              <div className="p-3">
            Base UI is a library of high-quality unstyled React components for design systems and
            web apps.
          </div>
        </Accordion.Panel>
          </Accordion.Item>

          <Accordion.Item className="border-b border-gray-200">
            <Accordion.Header>
              <Accordion.Trigger className="group relative flex w-full items-baseline justify-between gap-4 bg-gray-50 py-2 pr-1 pl-3 text-left font-medium hover:bg-gray-100 focus-visible:z-1 focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-800">
            How do I get started?
            <PlusIcon className="mr-2 size-3 shrink-0 transition-all ease-out group-data-[panel-open]:scale-110 group-data-[panel-open]:rotate-45" />
          </Accordion.Trigger>
        </Accordion.Header>
            <Accordion.Panel className="h-[var(--accordion-panel-height)] overflow-hidden text-base text-gray-600 transition-[height] ease-out data-[ending-style]:h-0 data-[starting-style]:h-0">
              <div className="p-3">
            Head to the “Quick start” guide in the docs. If you’ve used unstyled libraries before,
            you’ll feel at home.
          </div>
        </Accordion.Panel>
          </Accordion.Item>

          <Accordion.Item className="border-b border-gray-200">
            <Accordion.Header>
              <Accordion.Trigger className="group relative flex w-full items-baseline justify-between gap-4 bg-gray-50 py-2 pr-1 pl-3 text-left font-medium hover:bg-gray-100 focus-visible:z-1 focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-800">
            Can I use it for my project?
            <PlusIcon className="mr-2 size-3 shrink-0 transition-all ease-out group-data-[panel-open]:scale-110 group-data-[panel-open]:rotate-45" />
          </Accordion.Trigger>
        </Accordion.Header>
            <Accordion.Panel className="h-[var(--accordion-panel-height)] overflow-hidden text-base text-gray-600 transition-[height] ease-out data-[ending-style]:h-0 data-[starting-style]:h-0">
          <div className="p-3">Of course! Base UI is free and open source.</div>
        </Accordion.Panel>
      </Accordion.Item>
        </Accordion.Root>

);
}

function PlusIcon(props: React.ComponentProps<'svg'>) {
return (
<svg viewBox="0 0 12 12" fill="currentcolor" {...props}>
<path d="M6.75 0H5.25V5.25H0V6.75L5.25 6.75V12H6.75V6.75L12 6.75V5.25H6.75V0Z" />
</svg>
);
}

Anatomy

import { Accordion } from 'gnome-ui/accordion';

<Accordion.Root>
  <Accordion.Item>
    <Accordion.Header>
      <Accordion.Trigger />
    </Accordion.Header>
    <Accordion.Panel />
  </Accordion.Item>
</Accordion.Root>

Examples

Compact / Accent

Single-open accordion inspired by GNOME Settings preference rows.

Accordion component

Choose between Light, Dark, and High-Contrast themes.

import * as React from 'react';
import { Accordion } from 'gnome-ui/accordion';

export default function AccordionCompact() {
  return (
    <div className="w-[420px] max-w-[calc(100vw-2rem)] rounded-xl overflow-hidden border border-border bg-card shadow-sm">

      <div className="px-4 py-3 border-b border-border bg-muted/40">
        <p className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
          System Preferences
        </p>
      </div>

      <Accordion.Root defaultValue="appearance" className="flex flex-col divide-y divide-border">

        <Accordion.Item value="appearance">
          <Accordion.Header>
            <Accordion.Trigger className="group relative flex w-full items-center gap-3 px-4 py-3 text-left bg-card text-foreground hover:bg-accent transition-colors duration-150 focus-visible:z-10 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring data-[panel-open]:bg-primary/8">
              <span className="absolute left-0 top-2 bottom-2 w-0.5 rounded-full bg-primary scale-y-0 group-data-[panel-open]:scale-y-100 transition-transform duration-200 ease-out origin-center" />
              <span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-xl bg-secondary text-base leading-none">🎨</span>
              <span className="flex-1 text-sm font-medium">Appearance</span>
              <Icons.ChevronUpIcon className="mr-1 size-4 shrink-0 text-muted-foreground transition-transform duration-200 ease-out group-data-[panel-open]:rotate-180 group-data-[panel-open]:text-primary" />
            </Accordion.Trigger>
          </Accordion.Header>
          <Accordion.Panel className="h-[var(--accordion-panel-height)] overflow-hidden transition-[height] ease-out data-[starting-style]:h-0 data-[ending-style]:h-0">
            <div className="px-4 pb-4 pt-2 pl-[3.75rem] text-sm leading-relaxed text-muted-foreground">
              Choose between Light, Dark, and High-Contrast themes.
            </div>
          </Accordion.Panel>
        </Accordion.Item>

        <Accordion.Item value="display">
          <Accordion.Header>
            <Accordion.Trigger className="group relative flex w-full items-center gap-3 px-4 py-3 text-left bg-card text-foreground hover:bg-accent transition-colors duration-150 focus-visible:z-10 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring data-[panel-open]:bg-primary/8">
              <span className="absolute left-0 top-2 bottom-2 w-0.5 rounded-full bg-primary scale-y-0 group-data-[panel-open]:scale-y-100 transition-transform duration-200 ease-out origin-center" />
              <span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-xl bg-secondary text-base leading-none">🖥️</span>
              <span className="flex-1 text-sm font-medium">Displays</span>
              <Icons.ChevronUpIcon className="mr-1 size-4 shrink-0 text-muted-foreground transition-transform duration-200 ease-out group-data-[panel-open]:rotate-180 group-data-[panel-open]:text-primary" />
            </Accordion.Trigger>
          </Accordion.Header>
          <Accordion.Panel className="h-[var(--accordion-panel-height)] overflow-hidden transition-[height] ease-out data-[starting-style]:h-0 data-[ending-style]:h-0">
            <div className="px-4 pb-4 pt-2 pl-[3.75rem] text-sm leading-relaxed text-muted-foreground">
              Configure resolution, refresh rate, and multi-monitor layout.
            </div>
          </Accordion.Panel>
        </Accordion.Item>

        <Accordion.Item value="sound">
          <Accordion.Header>
            <Accordion.Trigger className="group relative flex w-full items-center gap-3 px-4 py-3 text-left bg-card text-foreground hover:bg-accent transition-colors duration-150 focus-visible:z-10 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring data-[panel-open]:bg-primary/8">
              <span className="absolute left-0 top-2 bottom-2 w-0.5 rounded-full bg-primary scale-y-0 group-data-[panel-open]:scale-y-100 transition-transform duration-200 ease-out origin-center" />
              <span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-xl bg-secondary text-base leading-none">🔊</span>
              <span className="flex-1 text-sm font-medium">Sound</span>
              <Icons.ChevronUpIcon className="mr-1 size-4 shrink-0 text-muted-foreground transition-transform duration-200 ease-out group-data-[panel-open]:rotate-180 group-data-[panel-open]:text-primary" />
            </Accordion.Trigger>
          </Accordion.Header>
          <Accordion.Panel className="h-[var(--accordion-panel-height)] overflow-hidden transition-[height] ease-out data-[starting-style]:h-0 data-[ending-style]:h-0">
            <div className="px-4 pb-4 pt-2 pl-[3.75rem] text-sm leading-relaxed text-muted-foreground">
              Adjust output volume and choose audio devices.
            </div>
          </Accordion.Panel>
        </Accordion.Item>

      </Accordion.Root>
    </div>
  );
}

function ChevronIcon(props: React.ComponentProps<'svg'>) {
  return (
    <svg viewBox="0 0 16 16" fill="currentColor" {...props}>
      <path d="M4.47 6.47a.75.75 0 0 1 1.06 0L8 8.94l2.47-2.47a.75.75 0 1 1 1.06 1.06l-3 3a.75.75 0 0 1-1.06 0l-3-3a.75.75 0 0 1 0-1.06Z" />
    </svg>
  );
}

Multiple-open accordion inspired by GNOME Files (Nautilus). The sidebar trigger and detail panel sit side by side inside each item.

Accordion component

Home Folder

Your personal home directory.

Path/home/ubuntuFree68.4 GB

Documents

Store your personal files and PDFs.

Items3Size2.1 MB

import * as React from 'react';
import { Accordion } from 'gnome-ui/accordion';

export default function AccordionSidebar() {
  return (
    <Accordion.Root
      multiple
      defaultValue={['home', 'documents']}
      orientation="horizontal"
      className="w-[520px] max-w-[calc(100vw-2rem)] rounded-xl overflow-hidden border border-border bg-card shadow-sm"
    >

      <Accordion.Item value="home" className="flex border-b border-border last:border-b-0">
        <Accordion.Header className="w-44 shrink-0 border-r border-sidebar-border bg-sidebar">
          <Accordion.Trigger className="group flex w-full items-center gap-2.5 px-3 py-3 text-left text-sidebar-foreground hover:bg-sidebar-accent transition-colors duration-150 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring data-[panel-open]:bg-sidebar-accent">
            <Icons.FolderIcon className="size-4 shrink-0 text-muted-foreground group-data-[panel-open]:text-sidebar-primary transition-colors duration-150" />
            <span className="flex-1 text-sm font-medium truncate">Home</span>
            <span className="mr-1 size-1.5 rounded-full bg-primary opacity-0 group-data-[panel-open]:opacity-100 transition-opacity duration-150" />
          </Accordion.Trigger>
        </Accordion.Header>
        <Accordion.Panel keepMounted className="flex-1 h-[var(--accordion-panel-height)] overflow-hidden transition-[height] ease-out data-[starting-style]:h-0 data-[ending-style]:h-0">
          <div className="mx-3 my-3 rounded-md border border-border bg-muted/30 p-3">
            <p className="text-xs font-semibold text-foreground mb-1">Home Folder</p>
            <p className="text-xs text-muted-foreground leading-relaxed">Your personal home directory.</p>
            <div className="mt-2 grid grid-cols-[auto_1fr] gap-x-3 gap-y-0.5">
              <span className="text-[11px] text-muted-foreground">Path</span>
              <span className="text-[11px] font-mono text-foreground">/home/ubuntu</span>
              <span className="text-[11px] text-muted-foreground">Free</span>
              <span className="text-[11px] font-mono text-foreground">68.4 GB</span>
            </div>
          </div>
        </Accordion.Panel>
      </Accordion.Item>

      <Accordion.Item value="documents" className="flex border-b border-border last:border-b-0">
        <Accordion.Header className="w-44 shrink-0 border-r border-sidebar-border bg-sidebar">
          <Accordion.Trigger className="group flex w-full items-center gap-2.5 px-3 py-3 text-left text-sidebar-foreground hover:bg-sidebar-accent transition-colors duration-150 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring data-[panel-open]:bg-sidebar-accent">
            <Icons.FolderIcon className="size-4 shrink-0 text-muted-foreground group-data-[panel-open]:text-sidebar-primary transition-colors duration-150" />
            <span className="flex-1 text-sm font-medium truncate">Documents</span>
            <span className="mr-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-primary/15 px-1 text-[10px] font-medium text-primary">3</span>
          </Accordion.Trigger>
        </Accordion.Header>
        <Accordion.Panel keepMounted className="flex-1 h-[var(--accordion-panel-height)] overflow-hidden transition-[height] ease-out data-[starting-style]:h-0 data-[ending-style]:h-0">
          <div className="mx-3 my-3 rounded-md border border-border bg-muted/30 p-3">
            <p className="text-xs font-semibold text-foreground mb-1">Documents</p>
            <p className="text-xs text-muted-foreground leading-relaxed">Store your personal files and PDFs.</p>
            <div className="mt-2 grid grid-cols-[auto_1fr] gap-x-3 gap-y-0.5">
              <span className="text-[11px] text-muted-foreground">Items</span>
              <span className="text-[11px] font-mono text-foreground">3</span>
              <span className="text-[11px] text-muted-foreground">Size</span>
              <span className="text-[11px] font-mono text-foreground">2.1 MB</span>
            </div>
          </div>
        </Accordion.Panel>
      </Accordion.Item>

      <Accordion.Item value="trash" className="flex border-b border-border last:border-b-0">
        <Accordion.Header className="w-44 shrink-0 border-r border-sidebar-border bg-sidebar">
          <Accordion.Trigger className="group flex w-full items-center gap-2.5 px-3 py-3 text-left text-sidebar-foreground hover:bg-sidebar-accent transition-colors duration-150 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring data-[panel-open]:bg-sidebar-accent">
            <Icons.FolderIcon className="size-4 shrink-0 text-muted-foreground group-data-[panel-open]:text-sidebar-primary transition-colors duration-150" />
            <span className="flex-1 text-sm font-medium truncate">Trash</span>
            <span className="mr-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-primary/15 px-1 text-[10px] font-medium text-primary">12</span>
          </Accordion.Trigger>
        </Accordion.Header>
        <Accordion.Panel keepMounted className="flex-1 h-[var(--accordion-panel-height)] overflow-hidden transition-[height] ease-out data-[starting-style]:h-0 data-[ending-style]:h-0">
          <div className="mx-3 my-3 rounded-md border border-border bg-muted/30 p-3">
            <p className="text-xs font-semibold text-foreground mb-1">Trash</p>
            <p className="text-xs text-muted-foreground leading-relaxed">Files remain here until you empty the trash.</p>
            <div className="mt-2 grid grid-cols-[auto_1fr] gap-x-3 gap-y-0.5">
              <span className="text-[11px] text-muted-foreground">Items</span>
              <span className="text-[11px] font-mono text-foreground">12 files</span>
              <span className="text-[11px] text-muted-foreground">Size</span>
              <span className="text-[11px] font-mono text-foreground">84.7 MB</span>
            </div>
          </div>
        </Accordion.Panel>
      </Accordion.Item>

    </Accordion.Root>
  );
}

function FolderIcon(props: React.ComponentProps<'svg'>) {
  return (
    <svg viewBox="0 0 16 16" fill="currentColor" {...props}>
      <path d="M1.75 3A1.75 1.75 0 0 0 0 4.75v7.5C0 13.216.784 14 1.75 14h12.5A1.75 1.75 0 0 0 16 12.25v-7a1.75 1.75 0 0 0-1.75-1.75H7.06l-.85-1.27A.75.75 0 0 0 5.6 2H1.75Z" />
    </svg>
  );
}

API reference

Root

Groups all parts of the accordion. Renders a <div> element.

Root Props:

PropTypeDefaultDescription
defaultValueany[]-The uncontrolled value of the item(s) that should be initially expanded.To render a controlled accordion, use the value prop instead.
valueany[]-The controlled value of the item(s) that should be expanded.To render an uncontrolled accordion, use the defaultValue prop instead.
onValueChange((value: any[], eventDetails: Accordion.Root.ChangeEventDetails) => void)-Event handler called when an accordion item is expanded or collapsed. Provides the new value as an argument.
hiddenUntilFoundbooleanfalseAllows 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.
loopFocusbooleantrueWhether to loop keyboard focus back to the first item when the end of the list is reached while using the arrow keys.
multiplebooleanfalseWhether multiple items can be open at the same time.
disabledbooleanfalseWhether the component should ignore user interaction.
orientationOrientation'vertical'The visual orientation of the accordion. Controls whether roving focus uses left/right or up/down arrow keys.
classNamestring | ((state: Accordion.Root.State) => string | undefined)-CSS class applied to the element, or a function that returns a class based on the component’s state.
styleCSSProperties | ((state: Accordion.Root.State) => CSSProperties | undefined)--
keepMountedbooleanfalseWhether to keep the element in the DOM while the panel is closed. This prop is ignored when hiddenUntilFound is used.
renderReactElement | ((props: HTMLProps, state: Accordion.Root.State) => ReactElement)-Allows you to replace the component’s HTML element with a different tag, or compose it with another component.Accepts a ReactElement or a function that returns the element to render.

Root Data Attributes:

AttributeTypeDescription
data-orientation-Indicates the orientation of the accordion.
data-disabled-Present when the accordion is disabled.

Item

Groups an accordion header with the corresponding panel. Renders a <div> element.

Item Props:

PropTypeDefaultDescription
valueany-A unique value that identifies this accordion item. If no value is provided, a unique ID will be generated automatically. Use when controlling the accordion programmatically, or to set an initial open state.
onOpenChange((open: boolean, eventDetails: Accordion.Item.ChangeEventDetails) => void)-Event handler called when the panel is opened or closed.
disabledbooleanfalseWhether the component should ignore user interaction.
classNamestring | ((state: Accordion.Item.State) => string | undefined)-CSS class applied to the element, or a function that returns a class based on the component’s state.
styleCSSProperties | ((state: Accordion.Item.State) => CSSProperties | undefined)--
renderReactElement | ((props: HTMLProps, state: Accordion.Item.State) => ReactElement)-Allows you to replace the component’s HTML element with a different tag, or compose it with another component.Accepts a ReactElement or a function that returns the element to render.

Item Data Attributes:

AttributeTypeDescription
data-open-Present when the accordion item is open.
data-disabled-Present when the accordion item is disabled.
data-indexnumberIndicates the index of the accordion item.

A heading that labels the corresponding panel. Renders an <h3> element.

Header Props:

PropTypeDefaultDescription
classNamestring | ((state: Accordion.Item.State) => string | undefined)-CSS class applied to the element, or a function that returns a class based on the component’s state.
styleCSSProperties | ((state: Accordion.Item.State) => CSSProperties | undefined)--
renderReactElement | ((props: HTMLProps, state: Accordion.Item.State) => ReactElement)-Allows you to replace the component’s HTML element with a different tag, or compose it with another component.Accepts a ReactElement or a function that returns the element to render.

Header Data Attributes:

AttributeTypeDescription
data-open-Present when the accordion item is open.
data-disabled-Present when the accordion item is disabled.
data-indexnumberIndicates the index of the accordion item.

Trigger

A button that opens and closes the corresponding panel. Renders a <button> element.

Trigger Props:

PropTypeDefaultDescription
nativeButtonbooleantrueWhether 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 (e.g. <div>).
classNamestring | ((state: Accordion.Item.State) => string | undefined)-CSS class applied to the element, or a function that returns a class based on the component’s state.
styleCSSProperties | ((state: Accordion.Item.State) => CSSProperties | undefined)--
renderReactElement | ((props: HTMLProps, state: Accordion.Item.State) => ReactElement)-Allows you to replace the component’s HTML element with a different tag, or compose it with another component.Accepts a ReactElement or a function that returns the element to render.

Trigger Data Attributes:

AttributeTypeDescription
data-panel-open-Present when the accordion panel is open.
data-disabled-Present when the accordion item is disabled.

Panel

A collapsible panel with the accordion item contents. Renders a <div> element.

Panel Props:

PropTypeDefaultDescription
hiddenUntilFoundbooleanfalseAllows 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.
classNamestring | ((state: Accordion.Panel.State) => string | undefined)-CSS class applied to the element, or a function that returns a class based on the component’s state.
styleCSSProperties | ((state: Accordion.Panel.State) => CSSProperties | undefined)--
keepMountedbooleanfalseWhether to keep the element in the DOM while the panel is closed. This prop is ignored when hiddenUntilFound is used.
renderReactElement | ((props: HTMLProps, state: Accordion.Panel.State) => ReactElement)-Allows you to replace the component’s HTML element with a different tag, or compose it with another component.Accepts a ReactElement or a function that returns the element to render.

Panel Data Attributes:

AttributeTypeDescription
data-open-Present when the accordion panel is open.
data-orientation-Indicates the orientation of the accordion.
data-disabled-Present when the accordion item is disabled.
data-indexnumberIndicates the index of the accordion item.
data-starting-style-Present when the panel is animating in.
data-ending-style-Present when the panel is animating out.

Panel CSS Variables:

VariableTypeDefaultDescription
--accordion-panel-heightnumber-The accordion panel's height.
--accordion-panel-widthnumber-The accordion panel's width.

On this page