Accordion
A set of collapsible panels with headings.
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.
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> ); }
Sidebar / Detail
Multiple-open accordion inspired by GNOME Files (Nautilus). The sidebar trigger and detail panel sit side by side inside each item.
Home Folder
Your personal home directory.
Documents
Store your personal files and PDFs.
Trash
Files remain here until you empty the trash.
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:
| Prop | Type | Default | Description |
|---|---|---|---|
| defaultValue | any[] | - | The uncontrolled value of the item(s) that should be initially expanded.To render a controlled accordion, use the value prop instead. |
| value | any[] | - | 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. |
| hiddenUntilFound | boolean | false | 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. |
| loopFocus | boolean | true | Whether to loop keyboard focus back to the first item when the end of the list is reached while using the arrow keys. |
| multiple | boolean | false | Whether multiple items can be open at the same time. |
| disabled | boolean | false | Whether the component should ignore user interaction. |
| orientation | Orientation | 'vertical' | The visual orientation of the accordion. Controls whether roving focus uses left/right or up/down arrow keys. |
| className | string | ((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. |
| style | CSSProperties | ((state: Accordion.Root.State) => CSSProperties | undefined) | - | - |
| keepMounted | boolean | false | Whether to keep the element in the DOM while the panel is closed.
This prop is ignored when hiddenUntilFound is used. |
| render | ReactElement | ((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:
| Attribute | Type | Description |
|---|---|---|
| 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:
| Prop | Type | Default | Description |
|---|---|---|---|
| value | any | - | 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. |
| disabled | boolean | false | Whether the component should ignore user interaction. |
| className | string | ((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. |
| style | CSSProperties | ((state: Accordion.Item.State) => CSSProperties | undefined) | - | - |
| render | ReactElement | ((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:
| Attribute | Type | Description |
|---|---|---|
| data-open | - | Present when the accordion item is open. |
| data-disabled | - | Present when the accordion item is disabled. |
| data-index | number | Indicates the index of the accordion item. |
Header
A heading that labels the corresponding panel.
Renders an <h3> element.
Header Props:
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | ((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. |
| style | CSSProperties | ((state: Accordion.Item.State) => CSSProperties | undefined) | - | - |
| render | ReactElement | ((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:
| Attribute | Type | Description |
|---|---|---|
| data-open | - | Present when the accordion item is open. |
| data-disabled | - | Present when the accordion item is disabled. |
| data-index | number | Indicates the index of the accordion item. |
Trigger
A button that opens and closes the corresponding panel.
Renders a <button> element.
Trigger Props:
| Prop | Type | Default | Description |
|---|---|---|---|
| nativeButton | boolean | true | 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 (e.g. <div>). |
| className | string | ((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. |
| style | CSSProperties | ((state: Accordion.Item.State) => CSSProperties | undefined) | - | - |
| render | ReactElement | ((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:
| Attribute | Type | Description |
|---|---|---|
| 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:
| Prop | Type | Default | Description |
|---|---|---|---|
| hiddenUntilFound | boolean | false | 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. |
| className | string | ((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. |
| style | CSSProperties | ((state: Accordion.Panel.State) => CSSProperties | undefined) | - | - |
| keepMounted | boolean | false | Whether to keep the element in the DOM while the panel is closed.
This prop is ignored when hiddenUntilFound is used. |
| render | ReactElement | ((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:
| Attribute | Type | Description |
|---|---|---|
| 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-index | number | Indicates 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:
| Variable | Type | Default | Description |
|---|---|---|---|
| --accordion-panel-height | number | - | The accordion panel's height. |
| --accordion-panel-width | number | - | The accordion panel's width. |