Accordion
A vertically stacked list of headers that reveal or hide associated sections of content.
import { Accordion, AccordionContent, AccordionHeader, AccordionItem,} from "@opengovsg/oui"export const Example = () => { return ( <Accordion> <AccordionItem> <AccordionHeader>What is OUI Design System?</AccordionHeader> <AccordionContent> OUI is a comprehensive design system built for OGP products, providing accessible and consistent components. </AccordionContent> </AccordionItem> <AccordionItem> <AccordionHeader>How do I get started?</AccordionHeader> <AccordionContent> Install the package using npm, yarn, or pnpm, then import the components you need into your React application. </AccordionContent> </AccordionItem> <AccordionItem> <AccordionHeader>Is it accessible?</AccordionHeader> <AccordionContent> Yes, all components follow WCAG 2.1 guidelines and include proper ARIA attributes for screen readers and keyboard navigation. </AccordionContent> </AccordionItem> </Accordion> )}Usage
OUI exports 4 accordion-related components:
- Accordion: The main wrapper component (uses React Aria's
DisclosureGroup). - AccordionItem: The component that wraps each disclosure item.
- AccordionHeader: The header button that toggles the accordion item.
- AccordionContent: The collapsible content panel.
import {
Accordion,
AccordionContent,
AccordionHeader,
AccordionItem,
} from "@opengovsg/oui"<Accordion>
<AccordionItem>
<AccordionHeader>Accordion header</AccordionHeader>
<AccordionContent>Accordion content</AccordionContent>
</AccordionItem>
</Accordion>Alternatively, install the component as local source via the shadcn CLI:
npx shadcn@latest add https://oui.open.gov.sg/r/accordion.jsonpnpm dlx shadcn@latest add https://oui.open.gov.sg/r/accordion.jsonnpx shadcn@latest add https://oui.open.gov.sg/r/accordion.jsonbunx --bun shadcn@latest add https://oui.open.gov.sg/r/accordion.jsonFeatures
The Accordion component provides an accessible way to organize and display collapsible content sections.
- Accessible – Follows the ARIA disclosure pattern with proper keyboard navigation and screen reader support.
- Flexible – Support for single or multiple expanded items, disabled states, and custom styling.
- Keyboard Navigation – Full keyboard support with Enter/Space to toggle items and Tab to navigate between them.
- Animated – Smooth expand/collapse animations with configurable transitions.
- Customizable – Support for custom icons, start/end content, and size variants.
Examples
Sizes
Use the size prop on Accordion to change the size of the accordion.
import { Accordion, AccordionContent, AccordionHeader, AccordionItem,} from "@opengovsg/oui"export const Example = () => { return ( <div className="flex w-full flex-col gap-8"> <div> <p className="text-base-content-medium mb-2 text-sm">Small</p> <Accordion size="sm"> <AccordionItem> <AccordionHeader>Small accordion item</AccordionHeader> <AccordionContent> This is a small sized accordion with compact spacing. </AccordionContent> </AccordionItem> </Accordion> </div> <div> <p className="text-base-content-medium mb-2 text-sm"> Medium (Default) </p> <Accordion size="md"> <AccordionItem> <AccordionHeader>Medium accordion item</AccordionHeader> <AccordionContent> This is a medium sized accordion with default spacing. </AccordionContent> </AccordionItem> </Accordion> </div> </div> )}Multiple Expanded Items
By default, only one accordion item can be expanded at a time. Use the allowsMultipleExpanded prop on the Accordion component to allow multiple items to be expanded simultaneously.
import { Accordion, AccordionContent, AccordionHeader, AccordionItem,} from "@opengovsg/oui"export const Example = () => { return ( <Accordion allowsMultipleExpanded> <AccordionItem> <AccordionHeader>Can multiple items be open?</AccordionHeader> <AccordionContent> Yes! With the allowsMultipleExpanded prop, you can expand multiple accordion items at the same time. </AccordionContent> </AccordionItem> <AccordionItem> <AccordionHeader>How does it work?</AccordionHeader> <AccordionContent> Simply add the allowsMultipleExpanded prop to the Accordion component to enable this behavior. </AccordionContent> </AccordionItem> <AccordionItem> <AccordionHeader>Try expanding all items</AccordionHeader> <AccordionContent> Click on all the headers and see how multiple sections can remain open simultaneously. </AccordionContent> </AccordionItem> </Accordion> )}Default Expanded
Use the defaultExpanded prop on AccordionItem to set an item as expanded by default.
import { Accordion, AccordionContent, AccordionHeader, AccordionItem,} from "@opengovsg/oui"export const Example = () => { return ( <Accordion defaultExpandedKeys={["item-1"]}> <AccordionItem id="item-1"> <AccordionHeader>This item is expanded by default</AccordionHeader> <AccordionContent> You can set an accordion item to be expanded by default using the defaultExpanded prop. </AccordionContent> </AccordionItem> <AccordionItem id="item-2"> <AccordionHeader>This item is collapsed by default</AccordionHeader> <AccordionContent> This content is hidden until you click on the header. </AccordionContent> </AccordionItem> </Accordion> )}Controlled
Control the expanded state of the accordion using the expandedKeys and onExpandedChange props on the Accordion component.
import type { Key } from "react-aria-components"import { useState } from "react"import { Accordion, AccordionContent, AccordionHeader, AccordionItem, Button,} from "@opengovsg/oui"export const Example = () => { const [expandedItems, setExpandedItems] = useState(new Set<Key>()) return ( <div className="flex w-full flex-col gap-4"> <div className="flex gap-2"> <Button size="sm" onPress={() => setExpandedItems(new Set(["1"]))}> Expand First </Button> <Button size="sm" onPress={() => setExpandedItems(new Set(["2"]))}> Expand Second </Button> <Button size="sm" onPress={() => setExpandedItems(new Set())}> Collapse All </Button> </div> <Accordion expandedKeys={expandedItems} onExpandedChange={setExpandedItems} > <AccordionItem id="1"> <AccordionHeader>First controlled item</AccordionHeader> <AccordionContent> This accordion's expanded state is controlled by the buttons above. </AccordionContent> </AccordionItem> <AccordionItem id="2"> <AccordionHeader>Second controlled item</AccordionHeader> <AccordionContent> Click the buttons above to control which items are expanded. </AccordionContent> </AccordionItem> </Accordion> </div> )}Start Content
Use the startContent prop on AccordionHeader to add content before the title, such as icons.
import { AlertCircle, HelpCircle, Info } from "lucide-react"import { Accordion, AccordionContent, AccordionHeader, AccordionItem,} from "@opengovsg/oui"export const Example = () => { return ( <Accordion> <AccordionItem> <AccordionHeader startContent={<Info />}> What happens if I lose my Secret Key? </AccordionHeader> <AccordionContent> If your form is live, duplicate your form, save the new secret key securely and replace the original form's link with the new form's link to continue collecting responses. Deactivate the original form as soon as possible to avoid losing further responses. </AccordionContent> </AccordionItem> <AccordionItem> <AccordionHeader startContent={<HelpCircle />}> How do I reset my password? </AccordionHeader> <AccordionContent> Click on the "Forgot Password" link on the login page and follow the instructions sent to your email. </AccordionContent> </AccordionItem> <AccordionItem> <AccordionHeader startContent={<AlertCircle />}> What should I do if I encounter an error? </AccordionHeader> <AccordionContent> Please contact our support team with the error details and we will assist you as soon as possible. </AccordionContent> </AccordionItem> </Accordion> )}Custom Indicator
Use the indicator prop on AccordionHeader to customize the expansion indicator icon.
import { Plus } from "lucide-react"import { Accordion, AccordionContent, AccordionHeader, AccordionItem,} from "@opengovsg/oui"export const Example = () => { return ( <Accordion> <AccordionItem> <AccordionHeader indicator={<Plus />}> Custom plus icon indicator </AccordionHeader> <AccordionContent> This accordion uses a custom plus icon as the indicator instead of the default chevron. </AccordionContent> </AccordionItem> <AccordionItem> <AccordionHeader indicator={<Plus />}> Another item with plus icon </AccordionHeader> <AccordionContent> The plus icon rotates 180 degrees when expanded, creating a visual effect. </AccordionContent> </AccordionItem> </Accordion> )}Hide Indicator
Use the hideIndicator prop on AccordionHeader to hide the indicator icon.
import { Accordion, AccordionContent, AccordionHeader, AccordionItem,} from "@opengovsg/oui"export const Example = () => { return ( <Accordion> <AccordionItem> <AccordionHeader hideIndicator>No indicator shown here</AccordionHeader> <AccordionContent> This accordion item has no indicator icon visible. </AccordionContent> </AccordionItem> <AccordionItem> <AccordionHeader hideIndicator> Another item without indicator </AccordionHeader> <AccordionContent> You can hide the indicator by setting the hideIndicator prop to true. </AccordionContent> </AccordionItem> </Accordion> )}Disabled
Use the isDisabled prop on AccordionItem to disable interaction with specific items.
import { Accordion, AccordionContent, AccordionHeader, AccordionItem,} from "@opengovsg/oui"export const Example = () => { return ( <Accordion> <AccordionItem> <AccordionHeader>This item is enabled</AccordionHeader> <AccordionContent> You can click on this item to expand and collapse it. </AccordionContent> </AccordionItem> <AccordionItem isDisabled> <AccordionHeader>This item is disabled</AccordionHeader> <AccordionContent> You cannot interact with this item because it is disabled. </AccordionContent> </AccordionItem> <AccordionItem> <AccordionHeader>Another enabled item</AccordionHeader> <AccordionContent> This item is also interactive and can be expanded. </AccordionContent> </AccordionItem> </Accordion> )}API Reference
Accordion
The Accordion component wraps React Aria's DisclosureGroup component and provides styling variants. It accepts all props from DisclosureGroup plus the following:
| Prop | Type | Default | Description |
|---|---|---|---|
size | "sm" | "md" | "md" | The size of all accordion items. |
color | "main" | "main" | The color scheme of all accordion items. |
allowsMultipleExpanded | boolean | false | Whether multiple items can be expanded at the same time. |
expandedKeys | Set<string> | - | The expanded items (controlled). |
defaultExpandedKeys | Set<string> | - | The default expanded items (uncontrolled). |
onExpandedChange | (keys: Set<string>) => void | - | Handler called when the expanded items change. |
children | ReactNode | - | The accordion items. |
classNames | SlotsToClasses<AccordionSlots> | - | Custom classes for the accordion slots. |
AccordionItem
The AccordionItem component wraps React Aria's Disclosure component. It accepts all props from Disclosure including:
| Prop | Type | Default | Description |
|---|---|---|---|
isDisabled | boolean | false | Whether the accordion item is disabled. |
defaultExpanded | boolean | false | Whether the accordion item is expanded by default (uncontrolled). |
isExpanded | boolean | - | Whether the accordion item is expanded (controlled). |
onExpandedChange | (isExpanded: boolean) => void | - | Handler called when the expanded state changes. |
children | ReactNode | - | The accordion header and content. |
classNames | SlotsToClasses<AccordionSlots> | - | Custom classes for the accordion item slots. |
AccordionHeader
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | (props: AccordionHeaderRenderProps) => ReactNode | - | The header title content. Supports render props. |
startContent | ReactNode | (props: AccordionHeaderRenderProps) => ReactNode | - | Content displayed before the title. Supports render props. |
endContent | ReactNode | (props: AccordionHeaderRenderProps) => ReactNode | - | Content displayed after the title. Supports render props. |
indicator | ReactNode | (props: AccordionHeaderRenderProps) => ReactNode | <ChevronDown /> | The expansion indicator icon. Supports render props. |
hideIndicator | boolean | false | Whether to hide the indicator icon. |
classNames | SlotsToClasses<...> | - | Custom classes for the header slots. |
AccordionHeaderRenderProps
When using render props for children, startContent, endContent, or indicator, the following props are available:
| Prop | Type | Description |
|---|---|---|
isExpanded | boolean | Whether the accordion item is currently expanded. |
isHovered | boolean | Whether the header button is hovered. |
isPressed | boolean | Whether the header button is pressed. |
isFocused | boolean | Whether the header button is focused. |
isFocusVisible | boolean | Whether the header button has keyboard focus visible. |
isDisabled | boolean | Whether the accordion item is disabled. |
AccordionContent
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | - | The content to display inside the accordion panel. |
classNames | SlotsToClasses<"panel" | "content"> | - | Custom classes for the content slots. |
Accessibility
The Accordion component follows the ARIA disclosure pattern:
- The header button has
aria-expandedset to indicate the expanded state. - The content panel is properly associated with its header using
aria-controls. - Keyboard navigation is fully supported:
- Enter or Space: Toggle the accordion item.
- Tab: Move focus between accordion items and other focusable elements.
- Screen readers announce the expanded state and relationship between headers and panels.