Skip to Content

Sidebar

Displays a list of links or actions to navigate between different sections.

Usage

OUI exports 5 sidebar-related components:

  • Sidebar: A high-level convenience component that auto-generates items from a data array.
  • SidebarRoot: The base container component that provides context and styling to all children.
  • SidebarItem: An individual navigation item rendered as a link.
  • SidebarList: A collapsible section that can contain nested items.
  • SidebarHeader: A non-interactive header element used for section titles.
import { Sidebar } from "@opengovsg/oui"
<Sidebar
  items={[
    { type: "header", children: "Section" },
    {
      startContent: <MailIcon />,
      onPress: () => alert("Inbox clicked"),
      children: "Inbox",
      tooltip: "Inbox",
    },
    {
      label: "Settings",
      startContent: <Wrench />,
      subItems: [
        {
          children: "Profile",
          onPress: () => alert("Profile clicked"),
          tooltip: "Profile",
        },
      ],
    },
  ]}
/>

There are two ways to use the sidebar:

  1. Data-driven (recommended): Pass an items array to the Sidebar component. This handles rendering and nesting automatically.
  2. Composition: Use SidebarRoot, SidebarItem, SidebarList, and SidebarHeader directly for full control over the structure.

Features

  • Accessible – Built on React Aria primitives (Link, Disclosure, Tooltip) with proper ARIA labels and keyboard navigation.
  • Collapsible – The entire sidebar can collapse to show only icons, with tooltips appearing on hover for accessibility.
  • Expandable sectionsSidebarList sections can independently expand and collapse, with support for both controlled and uncontrolled state.
  • Flexible – Use the high-level Sidebar component for quick setup, or compose individual components for custom layouts.
  • Internationalized – Expand/collapse labels are localized for supported locales.

Examples

Each SidebarItem is built on React Aria's Link component, so it accepts all Link props for navigation. Use href for standard URL navigation, or onPress for custom click handling.

// URL navigation
<SidebarItem href="/inbox" startContent={<MailIcon />}>
  Inbox
</SidebarItem>
 
// Custom press handler
<SidebarItem onPress={() => alert("Inbox clicked")} startContent={<MailIcon />}>
  Inbox
</SidebarItem>

When using the data-driven Sidebar component, pass these props directly in the items array:

const items = [
  { children: "Inbox", href: "/inbox", startContent: <MailIcon /> },
  {
    children: "Starred",
    onPress: () => navigate("/starred"),
    startContent: <Star />,
  },
]

Sizes

Use the size prop to change the density of the sidebar. The default size is md.

Medium (default)

Small

Composition

For full control over the sidebar structure, use the individual components directly instead of the items array.

import {
  SidebarHeader,
  SidebarItem,
  SidebarList,
  SidebarRoot,
} from "@opengovsg/oui"

Collapsible Sections

Use SidebarList items (or objects with subItems in the items array) to create expandable sections. Set defaultIsExpanded to control the initial expansion state.

Only Caret Toggle

By default, clicking anywhere on a SidebarList item toggles its expansion. Set onlyCaretToggle to true to restrict toggling to only the caret icon. This is useful when the section label itself should act as a navigable link.

When using onlyCaretToggle, pass navigation props (such as href) directly to the SidebarList component. The label becomes a Link element while the caret remains a separate toggle button.

Selected State

Use the isSelected prop on items to indicate the currently active navigation item. Top-level selected items receive a background highlight, while nested selected items display a left border accent instead.

isSelected accepts either a boolean or a () => boolean function, which is useful for dynamic route matching.

// Static
<SidebarItem isSelected>Active item</SidebarItem>
 
// Dynamic
<SidebarItem isSelected={() => pathname === "/inbox"}>Inbox</SidebarItem>

Collapsed

Set isCollapsed to true to collapse the sidebar to show only icons. When collapsed:

  • Text labels and endContent are hidden.
  • Headers are hidden.
  • Section expand/collapse is disabled.
  • Tooltips appear on hover using each item's tooltip prop for accessibility.

Every item should have a tooltip prop set when the sidebar supports collapsing. This ensures users can still identify each item when labels are hidden.

Customizing Tooltips

Use the tooltipProps and tooltipTriggerProps props on SidebarRoot (or Sidebar) to customise tooltip behavior globally for all items. These props are spread onto the underlying React Aria Tooltip and TooltipTrigger components respectively.

By default, collapsed sidebar tooltips use placement="right", offset={4}, and delay={0}.

Controlled Collapse

Use isCollapsed and onCollapsedChange together for controlled collapse state. This lets you manage the collapsed state externally, for example with a toggle button.

On mobile layouts, you can render the sidebar inside a Modal to create a drawer-style navigation panel. Use classNames to override the modal's positioning so it slides in from the side.

Slots

SidebarRoot / Sidebar

  • base: The root <nav> element.
  • ul: The <ul> list container.

SidebarItem

  • item: The <li> element wrapping each item.
  • label: The <Link> element containing the item content.

SidebarList

  • list: The <li> element wrapping the section.
  • section: The Disclosure wrapper.
  • item: The trigger element for expand/collapse.
  • label: The label content area.
  • chevron: The chevron icon.
  • chevronContainer: The wrapper around the chevron button.
  • nestedPanel: The DisclosurePanel containing nested items.

SidebarHeader

  • headerLi: The <li> element wrapping the header.
  • header: The <h2> element.

All components accept a classNames prop (via SidebarRoot) to override styles for specific slots:

<SidebarRoot
  classNames={{
    base: "custom-nav",
    item: "custom-item",
    label: "custom-label",
  }}
>
  {/* ... */}
</SidebarRoot>

API Reference

The Sidebar component is a high-level wrapper that generates items from a data array. It accepts all SidebarRoot props plus the following:

PropTypeDefaultDescription
itemsGeneratedSidebarItem[]-Array of item definitions to render.

Each item in the array can be one of three types:

Regular item – Rendered as SidebarItem:

PropTypeDefaultDescription
childrenReactNode-The item label.
startContentReactNode-Content before the label (typically an icon).
endContentReactNode-Content after the label.
tooltipstring-Tooltip shown when the sidebar is collapsed.
isSelectedboolean | () => boolean-Whether the item is currently selected.
hrefstring-Navigation URL (from React Aria LinkProps).
onPress() => void-Press handler (from React Aria LinkProps).

Header item – Rendered as SidebarHeader:

PropTypeDefaultDescription
type"header"-Discriminator to indicate a header item.
childrenReactNode-The header text.
startContentReactNode-Content before the header text.
endContentReactNode-Content after the header text.

List item – Rendered as SidebarList:

PropTypeDefaultDescription
labelReactNode-The section label.
subItemsGeneratedSidebarItem[]-Nested items (can contain any item type, including more lists).
startContentReactNode-Content before the label.
endContentReactNode-Content after the label.
tooltipstring-Tooltip shown when the sidebar is collapsed.
isSelectedboolean | () => boolean-Whether the section is currently selected.
defaultIsExpandedbooleanfalseInitial expansion state (uncontrolled).
isExpandedboolean-Expansion state (controlled).
onExpand(isExpanded: boolean) => void-Handler called when expansion state changes.
onlyCaretTogglebooleanfalseOnly allow toggling via the caret icon.

SidebarRoot

PropTypeDefaultDescription
size"sm" | "md""md"The size variant.
isCollapsedboolean-Whether the sidebar is collapsed (controlled).
defaultCollapsedbooleanfalseWhether the sidebar is collapsed by default.
onCollapsedChange(isCollapsed: boolean) => void-Handler called when the collapsed state changes.
tooltipPropsPartial<TooltipProps>-Props spread onto each item's Tooltip when collapsed.
tooltipTriggerPropsPartial<TooltipTriggerComponentProps>-Props spread onto each item's TooltipTrigger when collapsed.
classNamestring-Custom class for the root <nav> element.
classNamesSlotsToClasses<SidebarSlots>-Custom classes for individual slots.
childrenReactNode-Sidebar content (items, headers, lists).

SidebarItem

Extends React Aria's Link props.

PropTypeDefaultDescription
childrenReactNode-The item label.
startContentReactNode-Content before the label (typically an icon).
endContentReactNode-Content after the label.
tooltipstring-Tooltip shown when the sidebar is collapsed.
isSelectedboolean | () => boolean-Whether the item is currently selected.

SidebarList

PropTypeDefaultDescription
labelReactNode-The section label displayed in the trigger.
childrenReactNode-Nested items rendered inside the collapsible panel.
startContentReactNode-Content before the label.
endContentReactNode-Content after the label.
tooltipstring-Tooltip shown when the sidebar is collapsed.
isSelectedboolean | () => boolean-Whether the section is currently selected.
defaultIsExpandedbooleanfalseInitial expansion state (uncontrolled).
isExpandedboolean-Expansion state (controlled).
onExpand(isExpanded: boolean) => void-Handler called when expansion state changes.
onlyCaretTogglebooleanfalseRestrict expand/collapse to only the caret icon.
linkPropsLinkProps-Props for the link element (only used with onlyCaretToggle).

SidebarHeader

PropTypeDefaultDescription
childrenReactNode-The header text.
startContentReactNode-Content before the header text.
endContentReactNode-Content after the header text.

Accessibility

The Sidebar component follows accessible navigation patterns:

  • Built on React Aria's Link and Disclosure components with proper ARIA semantics.
  • The root element renders as a <nav> for landmark navigation.
  • Collapsible sections use aria-expanded to communicate their state.
  • When collapsed, each item uses its tooltip prop as aria-label to maintain screen reader accessibility.
  • Keyboard navigation is fully supported:
    • Tab: Move focus between sidebar items.
    • Enter or Space: Activate links or toggle section expansion.
  • Expand/collapse labels are internationalized for supported locales (en-SG, zh-SG, ms-SG, ta-SG).