Open UI

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.json
pnpm dlx shadcn@latest add https://oui.open.gov.sg/r/accordion.json
npx shadcn@latest add https://oui.open.gov.sg/r/accordion.json
bunx --bun shadcn@latest add https://oui.open.gov.sg/r/accordion.json

Features

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&apos;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&apos;s link with the new          form&apos;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 &quot;Forgot Password&quot; 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:

PropTypeDefaultDescription
size"sm" | "md""md"The size of all accordion items.
color"main""main"The color scheme of all accordion items.
allowsMultipleExpandedbooleanfalseWhether multiple items can be expanded at the same time.
expandedKeysSet<string>-The expanded items (controlled).
defaultExpandedKeysSet<string>-The default expanded items (uncontrolled).
onExpandedChange(keys: Set<string>) => void-Handler called when the expanded items change.
childrenReactNode-The accordion items.
classNamesSlotsToClasses<AccordionSlots>-Custom classes for the accordion slots.

AccordionItem

The AccordionItem component wraps React Aria's Disclosure component. It accepts all props from Disclosure including:

PropTypeDefaultDescription
isDisabledbooleanfalseWhether the accordion item is disabled.
defaultExpandedbooleanfalseWhether the accordion item is expanded by default (uncontrolled).
isExpandedboolean-Whether the accordion item is expanded (controlled).
onExpandedChange(isExpanded: boolean) => void-Handler called when the expanded state changes.
childrenReactNode-The accordion header and content.
classNamesSlotsToClasses<AccordionSlots>-Custom classes for the accordion item slots.

AccordionHeader

PropTypeDefaultDescription
childrenReactNode | (props: AccordionHeaderRenderProps) => ReactNode-The header title content. Supports render props.
startContentReactNode | (props: AccordionHeaderRenderProps) => ReactNode-Content displayed before the title. Supports render props.
endContentReactNode | (props: AccordionHeaderRenderProps) => ReactNode-Content displayed after the title. Supports render props.
indicatorReactNode | (props: AccordionHeaderRenderProps) => ReactNode<ChevronDown />The expansion indicator icon. Supports render props.
hideIndicatorbooleanfalseWhether to hide the indicator icon.
classNamesSlotsToClasses<...>-Custom classes for the header slots.

AccordionHeaderRenderProps

When using render props for children, startContent, endContent, or indicator, the following props are available:

PropTypeDescription
isExpandedbooleanWhether the accordion item is currently expanded.
isHoveredbooleanWhether the header button is hovered.
isPressedbooleanWhether the header button is pressed.
isFocusedbooleanWhether the header button is focused.
isFocusVisiblebooleanWhether the header button has keyboard focus visible.
isDisabledbooleanWhether the accordion item is disabled.

AccordionContent

PropTypeDefaultDescription
childrenReactNode-The content to display inside the accordion panel.
classNamesSlotsToClasses<"panel" | "content">-Custom classes for the content slots.

Accessibility

The Accordion component follows the ARIA disclosure pattern:

  • The header button has aria-expanded set 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.

On this page