---
title: Sidebar
description: App navigation on the inset background, in sections of items with captions.
---

```tsx
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](/deft/components/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`.

## Header and footer

```tsx
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](/deft/components/dot).

## Nested and current items

```tsx
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

| Prop | Type | Default | Description |
| - | - | - | - |
| `icon?` | `TablerIcon` | - | On Item. Leads the label. |
| `trailing?` | `ReactNode` | - | On Item. Sits on the right, such as a count. |
| `active?` | `boolean` | - | On Item. Applies the selected style and ignores `aria-current` for styling. |
| `level?` | `0 \| 1 \| 2` | `0` | On Item. Indents nested items. |
| `parent?` | `boolean` | - | On Item. Strengthens the text and icon of an ancestor of the current item. |
| `avatar?` | `ReactNode` | - | On User. Leads the name. |
| `render?` | `ReactElement \| function` | - | On Item, Pill and User. Renders a router link or a menu trigger. |

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](/deft/components/button); an `onClick` that calls `preventDefault()` keeps the drawer closed.
