Sidebar

A collapsible side navigation panel component.
1<Flex style={{ width: "100%", height: 480 }}>
2 <Sidebar defaultOpen>
3 <Sidebar.Header>
4 <Flex align="center" gap={3} style={{ padding: "var(--rs-space-2)" }}>
5 <IconButton size={4} aria-label="Logo">
6 <BellIcon width={24} height={24} />
7 </IconButton>
8 <Text size="regular" weight="medium" data-collapse-hidden>
9 Apsara
10 </Text>
11 </Flex>
12 </Sidebar.Header>
13 <Sidebar.Main>
14 <Sidebar.Item
15 href="#"

Anatomy

Import and assemble the component:

1import { Sidebar } from "@raystack/apsara";
2
3<Sidebar>
4 <Sidebar.Header>
5 <Sidebar.Trigger />
6 </Sidebar.Header>
7 <Sidebar.Main>
8 <Sidebar.Group label="Group name">
9 <Sidebar.Item href="#">Item</Sidebar.Item>
10 <Sidebar.More label="More">
11 <Sidebar.Item href="#">Hidden item</Sidebar.Item>
12 </Sidebar.More>
13 </Sidebar.Group>
14 </Sidebar.Main>
15 <Sidebar.Footer />
16</Sidebar>

API Reference

Root

Groups all parts of the sidebar navigation. Use collapsible={false} to hide the resize handle and prevent toggling. Use hideCollapsedItemTooltip to disable the tooltips the Sidebar adds to items (collapsed labels, and clipped labels when expanded). Use collapseMode to choose what the collapsed state looks like, and peekOnHover to preview a collapsed sidebar on hover.

Prop

Type

The header section is a container component that accepts all div props. It's commonly used to create a header with an icon and title.

Main

The main section wraps navigation groups and items. It accepts all div props and scrolls when content overflows.

Group

Prop

Type

Item

Note: leadingIcon is optional and will show a fallback avatar only in collapsed state. You can pass <></> to render truly nothing. Use the render prop to render as a custom element (e.g. a router Link).

Prop

Type

The footer section is a container that accepts all div props. It's commonly used for secondary links (e.g. Help, Preferences) and stays at the bottom of the sidebar.

Trigger

A button that toggles the sidebar open/closed. It works whether the Sidebar is controlled or uncontrolled, so you can drop it into a header without wiring up any state yourself. It accepts all IconButton props (e.g. size) plus the ones below.

Note: disabled automatically when the Sidebar has collapsible={false}. If you use collapseMode="hidden", don't place Sidebar.Trigger inside Sidebar.Header/Main/Footer — those sections are hidden along with the rest of the collapsed content, taking the trigger with them. Render it as a direct child of <Sidebar> instead, or drive open/onOpenChange yourself with a toggle button that lives outside <Sidebar> entirely (e.g. in your app's top bar).

Prop

Type

More

Renders a sidebar row that opens an Apsara menu with additional Sidebar.Item entries. It can be used inside Sidebar.Group or directly under Sidebar.Main / Sidebar.Footer.

Note: if leadingIcon is not provided, the trigger uses a default dots icon. In collapsed state, it follows the same item tooltip behavior and respects hideCollapsedItemTooltip.

Prop

Type

useSidebar

A hook that reads the sidebar state from context. Use it in custom header, footer, or item content to adapt to the collapsed state, or to toggle the sidebar from your own controls. It must be used inside <Sidebar>, otherwise it throws.

It returns:

  • isCollapsedtrue when the sidebar is collapsed (accounts for peekOnHover: false while peeking)
  • isPeekingtrue while a collapsed sidebar is being temporarily revealed via peekOnHover
  • opentrue when the sidebar is expanded
  • setOpen(open) — expand or collapse the sidebar
  • collapsible — whether the sidebar can be collapsed
  • position"left" or "right"
  • hideCollapsedItemTooltip — whether item tooltips are disabled in the collapsed state
1import { Sidebar, useSidebar } from "@raystack/apsara";
2
3const Workspace = () => {
4 const { isCollapsed } = useSidebar();
5 return isCollapsed ? <WorkspaceAvatar /> : <WorkspaceCard />;
6};
7
8<Sidebar>
9 <Sidebar.Header>
10 <Workspace />
11 </Sidebar.Header>
12</Sidebar>;

Note: for simple show/hide, the data-collapse-hidden attribute works without any JavaScript. Reach for useSidebar when you need to render different content instead of just hiding it.

Data attributes

Add data-collapse-hidden to any element inside the Sidebar to hide it automatically when the sidebar collapses, with no JavaScript or useSidebar wiring required. It animates alongside the collapse transition.

1<Sidebar.Header>
2 <Workspace />
3 <span data-collapse-hidden>Extra label, hidden when collapsed</span>
4</Sidebar.Header>

Examples

Position

The Sidebar can be positioned on either the left or right side of the screen.

1<Flex style={{ width: "100%", height: 480 }}>
2 <Sidebar open={true} position="left">
3 <Sidebar.Header>
4 <Flex align="center" gap={3} style={{ padding: "var(--rs-space-2)" }}>
5 <IconButton size={4} aria-label="Logo">
6 <BellIcon width={24} height={24} />
7 </IconButton>
8 <Text size="regular" weight="medium" data-collapse-hidden>
9 Apsara
10 </Text>
11 </Flex>
12 </Sidebar.Header>
13 <Sidebar.Main>
14 <Sidebar.Item
15 href="#"

Variants

Use variant to switch the Sidebar surface style:

  • plain (default): regular surface with side border
  • floating: lifted surface with shadow
  • inset: transparent surface without border or shadow
1<Flex style={{ width: "100%", height: 480 }}>
2 <Sidebar open={true} variant="plain">
3 <Sidebar.Header>
4 <Flex align="center" gap={3} style={{ padding: "var(--rs-space-2)" }}>
5 <IconButton size={4} aria-label="Logo">
6 <BellIcon width={24} height={24} />
7 </IconButton>
8 <Text size="regular" weight="medium" data-collapse-hidden>
9 Apsara
10 </Text>
11 </Flex>
12 </Sidebar.Header>
13 <Sidebar.Main>
14 <Sidebar.Item
15 href="#"

State

The Sidebar supports expanded and collapsed states with smooth transitions.

The data-collapse-hidden attribute can be used to conditionally hide elements when the sidebar is collapsed.

1<Flex style={{ width: "100%", height: 480 }}>
2 <Sidebar open={true}>
3 <Sidebar.Header>
4 <Flex align="center" gap={3} style={{ padding: "var(--rs-space-2)" }}>
5 <IconButton size={4} aria-label="Logo">
6 <BellIcon width={24} height={24} />
7 </IconButton>
8 <Text size="regular" weight="medium" data-collapse-hidden>
9 Apsara
10 </Text>
11 </Flex>
12 </Sidebar.Header>
13 <Sidebar.Main>
14 <Sidebar.Item
15 href="#"

Collapse mode

Use collapseMode to choose what the collapsed state looks like. Expanding works the same in both modes: the sidebar opens in place and pushes content.

  • "icon" (default): collapses to an icon rail.
  • "hidden": collapses to a thin strip with no visible content.

For a responsive layout, drive it from your own breakpoint check:

1<Sidebar collapseMode={isMobile ? 'hidden' : 'icon'} />

When collapseMode="hidden" is closed, the strip is just wide enough for the resize handle — reopen by clicking it, or place a Sidebar.Trigger as a direct child of <Sidebar> (not inside Header/Main/Footer, which are hidden along with everything else) for a clearer affordance, as in the "Hidden" tab below.

1<Flex style={{ width: "100%", height: 480 }}>
2 <Sidebar defaultOpen={false} collapseMode="icon">
3 <Sidebar.Header>
4 <Flex align="center" gap={3} style={{ padding: "var(--rs-space-2)" }}>
5 <IconButton size={4} aria-label="Logo">
6 <BellIcon width={24} height={24} />
7 </IconButton>
8 <Text size="regular" weight="medium" data-collapse-hidden>
9 Apsara
10 </Text>
11 </Flex>
12 </Sidebar.Header>
13 <Sidebar.Main>
14 <Sidebar.Item
15 href="#"

Peek on hover

Set peekOnHover to temporarily reveal a collapsed sidebar as an overlay above the content while the user hovers it, without pushing the content or changing the real open state — similar to VS Code's auto-hide sidebar. Moving the mouse away reverts it; clicking the resize handle or a Sidebar.Trigger pins it open for real (which does push content, like a normal open). Clicking a Sidebar.Item just navigates, like normal — it doesn't change the open state.

1<Flex style={{ width: "100%", height: 480 }}>
2 <Sidebar defaultOpen={false} collapseMode="icon" peekOnHover>
3 <Sidebar.Header>
4 <Flex align="center" gap={3} style={{ padding: "var(--rs-space-2)" }}>
5 <IconButton size={4} aria-label="Logo">
6 <BellIcon width={24} height={24} />
7 </IconButton>
8 <Text size="regular" weight="medium" data-collapse-hidden>
9 Apsara
10 </Text>
11 </Flex>
12 </Sidebar.Header>
13 <Sidebar.Main>
14 <Sidebar.Item
15 href="#"

Custom Tooltip Message

You can customize the tooltip message that appears when hovering over the collapse/expand handle using the collapseTooltip prop. The old tooltipMessage name still works, but is deprecated and will be removed in the next major version.

You can use Sidebar as a controlled component to conditionally render different tooltip messages.

1<Flex style={{ width: "100%", height: 480 }}>
2 <Sidebar defaultOpen collapseTooltip="Toggle navigation">
3 <Sidebar.Header>
4 <Flex align="center" gap={3} style={{ padding: "var(--rs-space-2)" }}>
5 <IconButton size={4} aria-label="Logo">
6 <BellIcon width={24} height={24} />
7 </IconButton>
8 <Text size="regular" weight="medium" data-collapse-hidden>
9 Apsara
10 </Text>
11 </Flex>
12 </Sidebar.Header>
13 <Sidebar.Main>
14 <Sidebar.Item
15 href="#"

Non-collapsible

Set collapsible={false} to hide the resize handle and prevent the sidebar from being collapsed or expanded.

1<Flex style={{ width: "100%", height: 480 }}>
2 <Sidebar defaultOpen collapsible={false}>
3 <Sidebar.Header>
4 <Flex align="center" gap={3} style={{ padding: "var(--rs-space-2)" }}>
5 <IconButton size={4} aria-label="Logo">
6 <BellIcon width={24} height={24} />
7 </IconButton>
8 <Text size="regular" weight="medium" data-collapse-hidden>
9 Apsara
10 </Text>
11 </Flex>
12 </Sidebar.Header>
13 <Sidebar.Main>
14 <Sidebar.Item
15 href="#"

Hide item tooltips when collapsed

Set hideCollapsedItemTooltip to disable the tooltips the Sidebar adds to navigation items — the label tooltip when collapsed, and the full-text tooltip on clipped labels when expanded. Useful when you show your own tooltips or want a cleaner collapsed state.

1<Flex style={{ width: "100%", height: 480 }}>
2 <Sidebar defaultOpen={false} hideCollapsedItemTooltip>
3 <Sidebar.Header>
4 <Flex align="center" gap={3} style={{ padding: "var(--rs-space-2)" }}>
5 <IconButton size={4} aria-label="Logo">
6 <BellIcon width={24} height={24} />
7 </IconButton>
8 <Text size="regular" weight="medium" data-collapse-hidden>
9 Apsara
10 </Text>
11 </Flex>
12 </Sidebar.Header>
13 <Sidebar.Main>
14 <Sidebar.Item href="#" leadingIcon={<BellIcon width={16} height={16} />}>
15 Overview

Group with icon

Pass leadingIcon to Sidebar.Group to render an icon next to the section label.

1<Flex style={{ width: "100%", height: 480 }}>
2 <Sidebar defaultOpen>
3 <Sidebar.Header>
4 <Flex align="center" gap={3} style={{ padding: "var(--rs-space-2)" }}>
5 <IconButton size={4} aria-label="Logo">
6 <BellIcon width={24} height={24} />
7 </IconButton>
8 <Text size="regular" weight="medium" data-collapse-hidden>
9 Apsara
10 </Text>
11 </Flex>
12 </Sidebar.Header>
13 <Sidebar.Main>
14 <Sidebar.Group
15 label="Workspace"

Collapsible Group

Enable collapsible on Sidebar.Group to render the group as an accordion whose items can be shown or hidden. You can also pass trailingIcon for section-level actions. Use defaultOpen to set the initial expanded state of an uncontrolled group.

Note: this is different from collapsible on the Sidebar root, which controls whether the whole sidebar can be collapsed.

1<Flex style={{ width: "100%", height: 480 }}>
2 <Sidebar defaultOpen>
3 <Sidebar.Header>
4 <Flex align="center" gap={3} style={{ padding: "var(--rs-space-2)" }}>
5 <IconButton size={4} aria-label="Logo">
6 <BellIcon width={24} height={24} />
7 </IconButton>
8 <Text size="regular" weight="medium" data-collapse-hidden>
9 Apsara
10 </Text>
11 </Flex>
12 </Sidebar.Header>
13 <Sidebar.Main>
14 <Sidebar.Item
15 href="#"

Controlled Group

Use open together with onOpenChange on Sidebar.Group to control the group's expanded state from outside the component.

1(function ControlledSidebarGroup() {
2 const [resourcesOpen, setResourcesOpen] = React.useState(true);
3
4 return (
5 <Flex style={{ width: "100%", height: 480 }}>
6 <Sidebar defaultOpen>
7 <Sidebar.Header>
8 <Flex align="center" gap={3} style={{ padding: "var(--rs-space-2)" }}>
9 <IconButton size={4} aria-label="Logo">
10 <BellIcon width={24} height={24} />
11 </IconButton>
12 <Text size="regular" weight="medium" data-collapse-hidden>
13 Apsara
14 </Text>
15 </Flex>

Trigger button

Place Sidebar.Trigger anywhere inside the Sidebar (typically Sidebar.Header) to add a visible toggle button next to the edge handle. No open/onOpenChange wiring is required.

1<Flex style={{ width: "100%", height: 480 }}>
2 <Sidebar defaultOpen>
3 <Sidebar.Header>
4 <Flex
5 align="center"
6 justify="between"
7 style={{ padding: "var(--rs-space-2)", width: "100%" }}
8 >
9 <Flex align="center" gap={3}>
10 <IconButton size={4} aria-label="Logo">
11 <BellIcon width={24} height={24} />
12 </IconButton>
13 <Text size="regular" weight="medium" data-collapse-hidden>
14 Apsara
15 </Text>

More

Use Sidebar.More when you want to keep a section compact and move secondary items into a menu.

1<Flex style={{ width: "100%", height: 480 }}>
2 <Sidebar defaultOpen>
3 <Sidebar.Header>
4 <Flex align="center" gap={3} style={{ padding: "var(--rs-space-2)" }}>
5 <IconButton size={4} aria-label="Logo">
6 <BellIcon width={24} height={24} />
7 </IconButton>
8 <Text size="regular" weight="medium" data-collapse-hidden>
9 Apsara
10 </Text>
11 </Flex>
12 </Sidebar.Header>
13 <Sidebar.Main>
14 <Sidebar.Item
15 href="#"

Accessibility

The Sidebar implements the following accessibility features:

  • Reduced motion — Sidebar collapse/expand motion is enabled only when prefers-reduced-motion: no-preference.

  • Proper ARIA roles and attributes

    • role="navigation" for the main sidebar
    • role="list" for item containers (Main, group items, Footer) and role="listitem" for navigation items and groups, forming valid nested lists
    • aria-expanded to indicate sidebar state, on both the sidebar and Sidebar.Trigger
    • aria-current="page" for active items
    • aria-disabled="true" for disabled items
    • Sidebar.Trigger announces "Collapse sidebar" or "Expand sidebar" based on state (override via aria-label)
  • Truncation handling

    • Long item and group labels truncate with an ellipsis
    • Items whose label is clipped show the full text in a tooltip on hover or focus (positioned away from the sidebar's edge based on position; disable with hideCollapsedItemTooltip)
  • Keyboard navigation support

    • Enter/Space to toggle sidebar expansion
    • Tab navigation through interactive elements
  • Screen reader support

    • Meaningful labels for all interactive elements
    • Hidden decorative elements
    • Clear state indicators
  • Known limitations

    • peekOnHover is mouse-only; there's no keyboard/focus equivalent that reveals the preview. Keyboard and screen reader users still get each item's full label via the existing per-item tooltip on focus, so no content is unreachable — they just don't get the hover preview shortcut.