Skip to content
OpenPerf
Esc
↑↓navigate↵open⌘Jpreview
On this page

Sidebar

App navigation on the inset background, in sections of items with captions.

import { Sidebar } from "@openperf/deft";

import { Preview } from "../../src/Preview";

export default function SidebarBasic() {
  return (
    <Preview overlay={true}>
      <Sidebar.Provider>
        <Sidebar.Trigger />
        <Sidebar.Root>
          <Sidebar.Content>
            <Sidebar.Section>
              <Sidebar.Caption>Section</Sidebar.Caption>
              <Sidebar.Item href="#item-1">Item 1</Sidebar.Item>
              <Sidebar.Item href="#item-2">Item 2</Sidebar.Item>
              <Sidebar.Item href="#item-3">Item 3</Sidebar.Item>
            </Sidebar.Section>
          </Sidebar.Content>
        </Sidebar.Root>
      </Sidebar.Provider>
    </Preview>
  );
}
  • Use it for product navigation in the application Shell: Header for workspace identity, scrollable Content for captioned sections, and Footer for utilities and the user.
  • No border and no search. The current item has the selected fill.
  • Pass render={<Link to="…" />} to items when using a router link.
  • Put Root and Trigger under one Provider so they share open state. Shell.Root already includes Provider.
  • From the md breakpoint up, Root is a sticky sidebar the height of the viewport and Trigger is hidden. Below it, Trigger opens the same navigation in a modal drawer with a backdrop and focus management; choosing an item closes it.
  • Root is labeled “Navigation” unless you pass an aria-label.
import { Avatar, Sidebar } from "@openperf/deft";

import { Preview } from "../../src/Preview";

export default function SidebarHeaderAndFooter() {
  return (
    <Preview overlay={true}>
      <Sidebar.Provider>
        <Sidebar.Trigger />
        <Sidebar.Root>
          <Sidebar.Header>
            <Sidebar.Pill>
              <Avatar.Root>A</Avatar.Root>
              Workspace
            </Sidebar.Pill>
          </Sidebar.Header>
          <Sidebar.Content>
            <Sidebar.Item aria-current="page" href="#item-1">
              Item 1
            </Sidebar.Item>
            <Sidebar.Item href="#item-2" trailing={12}>
              Item 2
            </Sidebar.Item>
          </Sidebar.Content>
          <Sidebar.Footer>
            <Sidebar.Item href="#item-3">Item 3</Sidebar.Item>
            <Sidebar.User avatar={<Avatar.Root>A</Avatar.Root>}>Name</Sidebar.User>
          </Sidebar.Footer>
        </Sidebar.Root>
      </Sidebar.Provider>
    </Preview>
  );
}
  • Docs, Support and Settings live in Sidebar.Footer with Sidebar.User.
  • Counts go in trailing, on the right in subtle text; alerts use a red Dot.

Nested and current items

import { Sidebar } from "@openperf/deft";
import { IconPlus } from "@tabler/icons-react";

import { Preview } from "../../src/Preview";

export default function SidebarNested() {
  return (
    <Preview overlay={true}>
      <Sidebar.Provider>
        <Sidebar.Trigger />
        <Sidebar.Root>
          <Sidebar.Content>
            <Sidebar.Item href="#item-1" icon={IconPlus}>
              Item 1
            </Sidebar.Item>
            <Sidebar.Item href="#item-2" icon={IconPlus} parent={true}>
              Item 2
            </Sidebar.Item>
            <Sidebar.Item href="#item-2-1" level={1} parent={true}>
              Item 2.1
            </Sidebar.Item>
            <Sidebar.Item aria-current="page" href="#item-2-1-1" level={2}>
              Item 2.1.1
            </Sidebar.Item>
          </Sidebar.Content>
        </Sidebar.Root>
      </Sidebar.Provider>
    </Preview>
  );
}
  • level={1} and level={2} indent nested items and mute their text; top-level items keep the default level={0}.
  • Set parent on each ancestor of the current item to strengthen its text and icon without the selected fill.
  • aria-current="page" on the current-page link marks it as selected.
  • active applies the selected style without setting aria-current. Once active is set, it alone decides the style, so a router link that marks itself current before the page loads does not highlight early.

API

PropType
icon?TablerIcon

On Item. Leads the label.

TypeTablerIcon
trailing?ReactNode

On Item. Sits on the right, such as a count.

TypeReactNode
active?boolean

On Item. Applies the selected style and ignores `aria-current` for styling.

Typeboolean
level?0 | 1 | 2

On Item. Indents nested items.

Type0 | 1 | 2
Default0
parent?boolean

On Item. Strengthens the text and icon of an ancestor of the current item.

Typeboolean
avatar?ReactNode

On User. Leads the name.

TypeReactNode
render?ReactElement | function

On Item, Pill and User. Renders a router link or a menu trigger.

TypeReactElement | function

Parts: Root, Caption, Content, Footer, Header, Item, Pill, Provider, Section, Trigger, User. Item renders an a, Pill and User a button, Content a nav and Root an aside, and Caption, Footer, Header and Section a div. Provider renders no element. Trigger takes the props of Button; an onClick that calls preventDefault() keeps the drawer closed.

Was this page helpful?