Accordion
A flexible Accordion component built on Base UI’s accessible primitives. It renders a set of collapsible panels with built-in keyboard navigation, ARIA support, and automatic focus handling, ensuring an accessible experience by default with a simple API for single or multiple open items.
import { Accordion } from "@/registry/rokkit200-ui/components/accordion/accordion";
const items = [ { id: "item-1", header: "What does this Accordion component provide?", panel: "This Accordion is a flexible wrapper around Base UI's accessible primitives. It adds layout, styling, selection modes (`single` | `multiple`), default open behavior, external control via ref (open/close), and customizable trigger rendering, while preserving accessibility and keyboard support.", }, { id: "item-2", header: "Can I open multiple items?", panel: "Yes. Set the `selectionMode` prop to `multiple` to allow more than one item to stay open at the same time.", }, { id: "item-3", header: "Is this accessible?", panel: "Yes. Accessibility is handled by Base UI, including proper ARIA attributes, keyboard navigation, focus management, and interaction states. This Accordion preserves those behaviors while adding layout and styling.", },];
export default function AccordionDefaultDemo() { return <Accordion items={items} />;}import { Accordion } from "@/rokkit200-ui/components/accordion/accordion";<Accordion items={items} />Examples
Section titled “Examples”Multiple
Section titled “Multiple”Setting selectionMode="multiple" determines whether multiple items can be open at the same time.
import { Accordion } from "@/registry/rokkit200-ui/components/accordion/accordion";
const items = [ { id: "item-1", header: "What does this Accordion component provide?", panel: "This Accordion is a flexible wrapper around Base UI's accessible primitives. It adds layout, styling, selection modes (`single` | `multiple`), default open behavior, external control via ref (open/close), and customizable trigger rendering, while preserving accessibility and keyboard support.", }, { id: "item-2", header: "Can I open multiple items?", panel: "Yes. Set the `selectionMode` prop to `multiple` to allow more than one item to stay open at the same time.", }, { id: "item-3", header: "Is this accessible?", panel: "Yes. Accessibility is handled by Base UI, including proper ARIA attributes, keyboard navigation, focus management, and interaction states. This Accordion preserves those behaviors while adding layout and styling.", },];
export default function AccordionMultipleDemo() { return <Accordion selectionMode="multiple" items={items} />;}Default Open First
Section titled “Default Open First”Setting defaultOpen="first" will expand the first item automatically.
import { Accordion } from "@/registry/rokkit200-ui/components/accordion/accordion";
const items = [ { id: "item-1", header: "What does this Accordion component provide?", panel: "This Accordion is a flexible wrapper around Base UI's accessible primitives. It adds layout, styling, selection modes (`single` | `multiple`), default open behavior, external control via ref (open/close), and customizable trigger rendering, while preserving accessibility and keyboard support.", }, { id: "item-2", header: "Can I open multiple items?", panel: "Yes. Set the `selectionMode` prop to `multiple` to allow more than one item to stay open at the same time.", }, { id: "item-3", header: "Is this accessible?", panel: "Yes. Accessibility is handled by Base UI, including proper ARIA attributes, keyboard navigation, focus management, and interaction states. This Accordion preserves those behaviors while adding layout and styling.", },];
export default function AccordionDefaultOpenFirstDemo() { return <Accordion defaultOpen="first" items={items} />;}Outside In Control
Section titled “Outside In Control”This demo illustrates the imperative control capabilities of the Accordion. By using the ref and the AccordionApi, a parent component can open or close accordion items programmatically, independent of user interactions. It highlights how the Accordion can be both user-driven and externally controlled.
import type { AccordionApi } from "@/registry/rokkit200-ui/components/accordion/accordion";import { Accordion } from "@/registry/rokkit200-ui/components/accordion/accordion";import { Button } from "@/registry/rokkit200-ui/components/button/button";import { useRef } from "react";
const items = [ { id: "item-1", header: "What does this Accordion component provide?", panel: "This Accordion is a flexible wrapper around Base UI's accessible primitives. It adds layout, styling, selection modes (`single` | `multiple`), default open behavior, external control via ref (open/close), and customizable trigger rendering, while preserving accessibility and keyboard support.", }, { id: "item-2", header: "Can I open multiple items?", panel: "Yes. Set the `selectionMode` prop to `multiple` to allow more than one item to stay open at the same time.", }, { id: "item-3", header: "Is this accessible?", panel: "Yes. Accessibility is handled by Base UI, including proper ARIA attributes, keyboard navigation, focus management, and interaction states. This Accordion preserves those behaviors while adding layout and styling.", },];
export default function AccordionOutsideInControlDemo() { const accordionRef = useRef<AccordionApi>(null);
return ( <div className="w-full"> <div className="mb-4 flex gap-2"> <Button variant="primary" onClick={() => accordionRef.current?.open("item-1")}> Open Item 1 </Button>
<Button variant="secondary" onClick={() => accordionRef.current?.open("item-2")}> Open Item 2 </Button>
<Button variant="outline" onClick={() => accordionRef.current?.close()}> Close All </Button> </div>
<Accordion ref={accordionRef} selectionMode="multiple" items={items} /> </div> );}Cross Communication
Section titled “Cross Communication”This demonstrates state synchronization between the Accordion and another UI element. In this specific case, the Accordion’s open/close state controls which images are displayed. As users interact with the Accordion, the images update in real time to reflect the current selection.
At the same time, the renderTrigger prop is used to customize the toggle icon, while function-based props such as headerClassName={(state) => ...}, itemClassName={(state) => ...}, and triggerClassName={(state) => ...} allow state-aware styling by receiving { id, index, open, openIds } for the current item during render.
import { cn } from "@/lib/utils";import { Accordion, type AccordionApi,} from "@/registry/rokkit200-ui/components/accordion/accordion";import { ContentImage } from "@/registry/rokkit200-ui/components/content-image/content-image";import type { NormalizedImageSource } from "@/registry/rokkit200-ui/components/image/image-types";
import { useRef, useState } from "react";
type AccordionDemoItem = { id: string; header: string; panel: string; src: NormalizedImageSource;};
const items: AccordionDemoItem[] = [ { id: "item-1", header: "Item 1", panel: "Content 1", src: { src: "https://placehold.co/250x240", width: 250, height: 240, metadata: { alt: "Accordion photo", }, }, }, { id: "item-2", header: "Item 2", panel: "Content 2", src: { src: "https://placehold.co/250x260", width: 250, height: 260, metadata: { alt: "Accordion photo", }, }, }, { id: "item-3", header: "Item 3", panel: "Content 3", src: { src: "https://placehold.co/250x280", width: 250, height: 280, metadata: { alt: "Accordion photo", }, }, },];
function PlusMinusIcon({ open }: { open: boolean }) { return ( <svg viewBox="0 0 12 12" fill="currentColor" className="mr-2 box-border h-3 w-3"> {open ? ( <path d="M0 5.25H12V6.75H0z" /> ) : ( <> <path d="M5.25 0H6.75V12H5.25z" /> <path d="M0 5.25H12V6.75H0z" /> </> )} </svg> );}
export default function AccordionCrossCommunication() { const accordionRef = useRef<AccordionApi>(null);
const [openItems, setOpenItems] = useState<string[]>([items[0].id]);
const selectedImages: AccordionDemoItem[] = openItems.length > 0 ? items.filter(item => openItems.includes(item.id)) : [ { id: "fallback", src: { src: "https://placehold.co/800x500", width: 800, height: 500, metadata: { alt: "Accordion photo", }, }, header: "No selection", panel: "", }, ];
return ( <div className="flex w-full gap-6"> <div className="w-3/5"> <Accordion ref={accordionRef} items={items} defaultOpen="first" onChange={setOpenItems} headerClassName={({ open }) => open ? "text-green-400 dark:text-green-400" : "text-red-400 dark:text-red-400" } itemClassName={({ open }) => cn( "bg-red-800 dark:bg-red-800 transition-colors duration-150 ease-out", open ? "bg-green-400 dark:bg-green-400" : "bg-red-800 dark:bg-red-800" ) } triggerClassName={({ open }) => open ? "bg-green-700 hover:bg-green-800 dark:bg-green-700 hover:dark:bg-green-800" : "bg-red-600 hover:bg-red-800 dark:bg-red-600 hover:dark:bg-red-800" } renderTrigger={open => <PlusMinusIcon open={open} />} /> </div>
<div className="flex w-2/5 flex-col gap-4"> {selectedImages.map((item, index) => ( <ContentImage key={index} imageSource={item.src} profile={{ id: "content", srcSetWidths: [400, 600, 800], sizes: ["(max-width: 600px) 100vw", "800px"], }} /> ))} </div> </div> );}API Reference
Section titled “API Reference”| Prop | Type | Default | Description |
|---|---|---|---|
items | AccordionItem[] | — | Array of accordion items. Each item has an id, header, and panel content. Required. |
headerTag | keyof React.JSX.IntrinsicElements | "h3" | Determines which HTML element is used for the accordion item header container (for example "h3", "h4", "div"). Useful for maintaining correct document heading hierarchy. Optional. |
selectionMode | "single" | "multiple" | "single" | Determines whether only one item can be open at a time or multiple items can stay open. Optional. |
defaultOpen | "none" | "first" | "none" | Determines which item is open by default. "first" opens the first item in the list. "none" keeps all items closed initially. Optional. |
className | string | — | Applied to the root Accordion container. Useful for styling with Tailwind or custom classes. Optional. |
itemClassName | string | ((state: ItemState) => string | undefined | null | false) | — | Applied to each <Accordion.Item> element. Can be a function receiving { id, index, open, openIds } to enable state-aware styling. Optional. |
headerClassName | string | ((state: ItemState) => string | undefined | null | false) | — | Applied to each <Accordion.Header> element. Supports function-based styling using the item state. Optional. |
triggerClassName | string | ((state: ItemState) => string | undefined | null | false) | — | Applied to each <Accordion.Trigger> element. Can dynamically style the trigger based on the item’s open state. Optional. |
triggerIconClassName | string | ((state: ItemState) => string | undefined | null | false) | — | Applied to the default trigger icon PlusIcon. When using renderTrigger, styling should be handled inside the custom render function. Optional. |
panelClassName | string | ((state: ItemState) => string | undefined | null | false) | — | Applied to each <Accordion.Panel> element (the collapsible content container). Supports state-aware styling. Optional. |
contentClassName | string | ((state: ItemState) => string | undefined | null | false) | — | Applied to the inner content wrapper inside each panel. Optional. |
onChange | (openItems: string[]) => void | — | Callback fired whenever the open items change. Provides the array of currently open item IDs. Useful for syncing accordion state with other UI (See Cross Communication example). Optional. |
renderTrigger | ((open: boolean) => React.ReactNode) | React.ReactNode | — | Allows custom rendering of the trigger icon or content. If a function is provided, it receives the open state of the item. If a React node is provided, it will replace the default icon entirely. Optional. |
See the Base UI documentation for more information.
Accessibility
Section titled “Accessibility”Accessibility is a fundamental part of Base UI’s Accordion. It takes care of complex details such as ARIA and role attributes, keyboard navigation, pointer interactions, and focus management automatically. This ensures a fully accessible experience by default, while maintaining a clear and intuitive API for customization.
See the Base UI documentation for more information.