Skip to main content

Sidebar

Sidebar is a navigation aid that lets people move between an app's main sections, such as a dashboard, inbox, or settings.

Import

import { Sidebar } from '@vortexlabs/vortex';
import type { SidebarProps } from '@vortexlabs/vortex';

Other links

Members
import { Button, DropdownMenu, Sidebar, Text } from "@vortexlabs/vortex";
import {
  Folder,
  Gear,
  Grid,
  Inbox,
  Sidebar as SidebarIcon,
  Users,
} from "@vortexlabs/vortex-icons";

export const Example = () => (
  <Sidebar.Root>
    <div style={{ display: "flex" }}>
      <Sidebar.Nav>
        <Sidebar.Header>
          <Text size="body-sm-desktop" weight="medium">
            Acme Inc
          </Text>
        </Sidebar.Header>
        <Sidebar.Content>
          <Sidebar.Group>
            <Sidebar.Item href="#home" icon={Grid} current>
              Home
            </Sidebar.Item>
            <Sidebar.Item href="#inbox" icon={Inbox} count={3} countAriaLabel="3 unread">
              Inbox
            </Sidebar.Item>
          </Sidebar.Group>
          <Sidebar.Group label="Workspace">
            <Sidebar.Parent label="Projects" icon={Folder}>
              <Sidebar.Item href="#active">Active</Sidebar.Item>
              <Sidebar.Item href="#archived">Archived</Sidebar.Item>
            </Sidebar.Parent>
            <Sidebar.Item href="#members" icon={Users}>
              Members
            </Sidebar.Item>
          </Sidebar.Group>
        </Sidebar.Content>
        <Sidebar.Footer>
          <Sidebar.Item href="#settings" icon={Gear}>
            Settings
          </Sidebar.Item>
          <Sidebar.Account name="Alex Chen" description="Admin" initials="AC">
            <DropdownMenu.Item>Profile</DropdownMenu.Item>
            <DropdownMenu.Item>Sign out</DropdownMenu.Item>
          </Sidebar.Account>
        </Sidebar.Footer>
      </Sidebar.Nav>
      <main>
        <Sidebar.Trigger>
          {({ onClick, ...attributes }) => (
            <Button
              variant="tertiary"
              size="sm"
              onlyIcon={SidebarIcon}
              ariaLabel="Open sidebar"
              onClick={onClick}
              attributes={attributes}
            />
          )}
        </Sidebar.Trigger>
        <Text weight="medium">Members</Text>
      </main>
    </div>
  </Sidebar.Root>
);

Features

  • Collapses to an icon rail, with nested items in a menu.
  • Groups items under labels that can collapse.
  • Shows counts, end icons, and the current page.
  • Includes an account menu and a callout area.
  • Becomes a drawer on narrow screens.

Anatomy

<Sidebar.Root>
  <Sidebar.Nav>
    <Sidebar.Header />
    <Sidebar.Content>
      <Sidebar.Group label="Workspace">
        <Sidebar.Item href="/home">Home</Sidebar.Item>
        <Sidebar.Item href="/members">Members</Sidebar.Item>
        <Sidebar.Parent label="Projects">
          <Sidebar.Item href="/active">Active</Sidebar.Item>
          <Sidebar.Item href="/archived">Archived</Sidebar.Item>
        </Sidebar.Parent>
      </Sidebar.Group>
    </Sidebar.Content>
    <Sidebar.Callout>Your trial ends in 3 days</Sidebar.Callout>
    <Sidebar.Footer>
      <Sidebar.Account name="Alex Chen" initials="AC">
        <DropdownMenu.Item>Sign out</DropdownMenu.Item>
      </Sidebar.Account>
    </Sidebar.Footer>
  </Sidebar.Nav>
  <Sidebar.Trigger>
    {({ onClick, ...attributes }) => (
      <Button onClick={onClick} attributes={attributes}>Menu</Button>
    )}
  </Sidebar.Trigger>
</Sidebar.Root>
Component Part
Description
Holds the rail and drawer state for the parts inside it.
Renders the navigation landmark, or a drawer on narrow screens.
Shows optional content beside the button that toggles the rail or closes the drawer.
Scrolls the navigation and slides a shared highlight between its items.
Groups items under an optional label that can collapse.
Links to a page or runs an action.
Reveals nested items inline, or in a menu from the rail.
Shows a message above the footer, hidden in the rail.
Keeps items and the account menu at the bottom.
Shows the signed-in person and opens their account menu.
Renders the button that opens the drawer below md.

Rail

Set defaultCollapsed to start with the 56 px icon rail. Labels move into tooltips, and nested items open in a menu beside their parent.

Members
import { Button, DropdownMenu, Sidebar, Text } from "@vortexlabs/vortex";
import {
  Folder,
  Gear,
  Grid,
  Inbox,
  Sidebar as SidebarIcon,
  Users,
} from "@vortexlabs/vortex-icons";

export const Example = () => (
  <Sidebar.Root defaultCollapsed>
    <div style={{ display: "flex" }}>
      <Sidebar.Nav>
        <Sidebar.Header>
          <Text size="body-sm-desktop" weight="medium">
            Acme Inc
          </Text>
        </Sidebar.Header>
        <Sidebar.Content>
          <Sidebar.Group>
            <Sidebar.Item href="#home" icon={Grid} current>
              Home
            </Sidebar.Item>
            <Sidebar.Item href="#inbox" icon={Inbox} count={3} countAriaLabel="3 unread">
              Inbox
            </Sidebar.Item>
          </Sidebar.Group>
          <Sidebar.Group label="Workspace">
            <Sidebar.Parent label="Projects" icon={Folder}>
              <Sidebar.Item href="#active">Active</Sidebar.Item>
              <Sidebar.Item href="#archived">Archived</Sidebar.Item>
            </Sidebar.Parent>
            <Sidebar.Item href="#members" icon={Users}>
              Members
            </Sidebar.Item>
          </Sidebar.Group>
        </Sidebar.Content>
        <Sidebar.Footer>
          <Sidebar.Item href="#settings" icon={Gear}>
            Settings
          </Sidebar.Item>
          <Sidebar.Account name="Alex Chen" description="Admin" initials="AC">
            <DropdownMenu.Item>Profile</DropdownMenu.Item>
            <DropdownMenu.Item>Sign out</DropdownMenu.Item>
          </Sidebar.Account>
        </Sidebar.Footer>
      </Sidebar.Nav>
      <main>
        <Sidebar.Trigger>
          {({ onClick, ...attributes }) => (
            <Button
              variant="tertiary"
              size="sm"
              onlyIcon={SidebarIcon}
              ariaLabel="Open sidebar"
              onClick={onClick}
              attributes={attributes}
            />
          )}
        </Sidebar.Trigger>
        <Text weight="medium">Members</Text>
      </main>
    </div>
  </Sidebar.Root>
);

Controlled State

Pass collapsed and onCollapsedChange to own the rail state, such as to remember the choice across reloads.

Members
import { Button, DropdownMenu, Sidebar, Text } from "@vortexlabs/vortex";
import {
  Folder,
  Gear,
  Grid,
  Inbox,
  Sidebar as SidebarIcon,
  Users,
} from "@vortexlabs/vortex-icons";
import React from "react";

export const Example = () => {
  const [collapsed, setCollapsed] = React.useState(false);

  return (
    <>
      <Button variant="secondary" size="sm" onClick={() => setCollapsed(!collapsed)}>
        {collapsed ? "Expand" : "Collapse"}
      </Button>
      <Sidebar.Root collapsed={collapsed} onCollapsedChange={setCollapsed}>
        <div style={{ display: "flex" }}>
          <Sidebar.Nav>
            <Sidebar.Header>
              <Text size="body-sm-desktop" weight="medium">
                Acme Inc
              </Text>
            </Sidebar.Header>
            <Sidebar.Content>
              <Sidebar.Group>
                <Sidebar.Item href="#home" icon={Grid} current>
                  Home
                </Sidebar.Item>
                <Sidebar.Item href="#inbox" icon={Inbox} count={3} countAriaLabel="3 unread">
                  Inbox
                </Sidebar.Item>
              </Sidebar.Group>
              <Sidebar.Group label="Workspace">
                <Sidebar.Parent label="Projects" icon={Folder}>
                  <Sidebar.Item href="#active">Active</Sidebar.Item>
                  <Sidebar.Item href="#archived">Archived</Sidebar.Item>
                </Sidebar.Parent>
                <Sidebar.Item href="#members" icon={Users}>
                  Members
                </Sidebar.Item>
              </Sidebar.Group>
            </Sidebar.Content>
            <Sidebar.Footer>
              <Sidebar.Item href="#settings" icon={Gear}>
                Settings
              </Sidebar.Item>
              <Sidebar.Account name="Alex Chen" description="Admin" initials="AC">
                <DropdownMenu.Item>Profile</DropdownMenu.Item>
                <DropdownMenu.Item>Sign out</DropdownMenu.Item>
              </Sidebar.Account>
            </Sidebar.Footer>
          </Sidebar.Nav>
          <main>
            <Sidebar.Trigger>
              {({ onClick, ...attributes }) => (
                <Button
                  variant="tertiary"
                  size="sm"
                  onlyIcon={SidebarIcon}
                  ariaLabel="Open sidebar"
                  onClick={onClick}
                  attributes={attributes}
                />
              )}
            </Sidebar.Trigger>
            <Text weight="medium">Members</Text>
          </main>
        </div>
      </Sidebar.Root>
    </>
  );
};

Groups

label names the group and becomes a divider in the rail. Set collapsible to let the label toggle the items.

Members
import { Button, Sidebar, Text } from "@vortexlabs/vortex";
import {
  File,
  Sidebar as SidebarIcon,
  Users,
} from "@vortexlabs/vortex-icons";

export const Example = () => (
  <Sidebar.Root>
    <div style={{ display: "flex" }}>
      <Sidebar.Nav>
        <Sidebar.Header />
        <Sidebar.Content>
          <Sidebar.Group label="Workspace">
            <Sidebar.Item href="#documents" icon={File}>
              Documents
            </Sidebar.Item>
            <Sidebar.Item href="#members" icon={Users}>
              Members
            </Sidebar.Item>
          </Sidebar.Group>
          <Sidebar.Group label="Reports" collapsible defaultExpanded={false}>
            <Sidebar.Item href="#usage" icon={File}>
              Usage
            </Sidebar.Item>
          </Sidebar.Group>
        </Sidebar.Content>
      </Sidebar.Nav>
      <main>
        <Sidebar.Trigger>
          {({ onClick, ...attributes }) => (
            <Button
              variant="tertiary"
              size="sm"
              onlyIcon={SidebarIcon}
              ariaLabel="Open sidebar"
              onClick={onClick}
              attributes={attributes}
            />
          )}
        </Sidebar.Trigger>
        <Text weight="medium">Members</Text>
      </main>
    </div>
  </Sidebar.Root>
);

Nested Items

Use Sidebar.Parent to nest items one level. A parent or collapsible group that contains the current item opens regardless of defaultExpanded.

Members
import { Button, Sidebar, Text } from "@vortexlabs/vortex";
import {
  Folder,
  Sidebar as SidebarIcon,
} from "@vortexlabs/vortex-icons";

export const Example = () => (
  <Sidebar.Root>
    <div style={{ display: "flex" }}>
      <Sidebar.Nav>
        <Sidebar.Header />
        <Sidebar.Content>
          <Sidebar.Group>
            <Sidebar.Parent label="Projects" icon={Folder} defaultExpanded>
              <Sidebar.Item href="#active" current>
                Active
              </Sidebar.Item>
              <Sidebar.Item href="#archived">Archived</Sidebar.Item>
            </Sidebar.Parent>
          </Sidebar.Group>
        </Sidebar.Content>
      </Sidebar.Nav>
      <main>
        <Sidebar.Trigger>
          {({ onClick, ...attributes }) => (
            <Button
              variant="tertiary"
              size="sm"
              onlyIcon={SidebarIcon}
              ariaLabel="Open sidebar"
              onClick={onClick}
              attributes={attributes}
            />
          )}
        </Sidebar.Trigger>
        <Text weight="medium">Members</Text>
      </main>
    </div>
  </Sidebar.Root>
);

Counts

Use count for a quiet number after the label. Set countAriaLabel to announce what it counts.

Members
import { Button, Sidebar, Text } from "@vortexlabs/vortex";
import {
  Bell,
  Inbox,
  Sidebar as SidebarIcon,
} from "@vortexlabs/vortex-icons";

export const Example = () => (
  <Sidebar.Root>
    <div style={{ display: "flex" }}>
      <Sidebar.Nav>
        <Sidebar.Header />
        <Sidebar.Content>
          <Sidebar.Group>
            <Sidebar.Item href="#inbox" icon={Inbox} count={3} countAriaLabel="3 unread">
              Inbox
            </Sidebar.Item>
            <Sidebar.Item href="#notifications" icon={Bell} count={12} countAriaLabel="12 new">
              Notifications
            </Sidebar.Item>
          </Sidebar.Group>
        </Sidebar.Content>
      </Sidebar.Nav>
      <main>
        <Sidebar.Trigger>
          {({ onClick, ...attributes }) => (
            <Button
              variant="tertiary"
              size="sm"
              onlyIcon={SidebarIcon}
              ariaLabel="Open sidebar"
              onClick={onClick}
              attributes={attributes}
            />
          )}
        </Sidebar.Trigger>
        <Text weight="medium">Members</Text>
      </main>
    </div>
  </Sidebar.Root>
);

Callout and Account

Sidebar.Callout holds a message above the footer. Sidebar.Account renders its children inside a DropdownMenu.

Members
import { Button, DropdownMenu, ProgressBar, Sidebar, Stack, Text } from "@vortexlabs/vortex";
import {
  Grid,
  Sidebar as SidebarIcon,
} from "@vortexlabs/vortex-icons";

export const Example = () => (
  <Sidebar.Root>
    <div style={{ display: "flex" }}>
      <Sidebar.Nav>
        <Sidebar.Header />
        <Sidebar.Content>
          <Sidebar.Group>
            <Sidebar.Item href="#home" icon={Grid}>
              Home
            </Sidebar.Item>
          </Sidebar.Group>
        </Sidebar.Content>
        <Sidebar.Callout>
          <Stack gap={3} padding={3} borderColor="default" borderRadius="sm" backgroundColor="default">
            <Stack gap={1}>
              <Text size="body-sm-desktop" weight="medium">
                Your trial ends in 3 days
              </Text>
              <Text size="body-xs-desktop" color="subtle">
                Upgrade to keep your projects and members.
              </Text>
            </Stack>
            <ProgressBar value={80} ariaLabel="Trial days used" />
            <Button variant="secondary" size="sm" fullWidth>
              Upgrade
            </Button>
          </Stack>
        </Sidebar.Callout>
        <Sidebar.Footer divider>
          <Sidebar.Account name="Alex Chen" description="Admin" initials="AC">
            <DropdownMenu.Item>Profile</DropdownMenu.Item>
            <DropdownMenu.Item>Sign out</DropdownMenu.Item>
          </Sidebar.Account>
        </Sidebar.Footer>
      </Sidebar.Nav>
      <main>
        <Sidebar.Trigger>
          {({ onClick, ...attributes }) => (
            <Button
              variant="tertiary"
              size="sm"
              onlyIcon={SidebarIcon}
              ariaLabel="Open sidebar"
              onClick={onClick}
              attributes={attributes}
            />
          )}
        </Sidebar.Trigger>
        <Text weight="medium">Members</Text>
      </main>
    </div>
  </Sidebar.Root>
);

Narrow Screens

Below md, Sidebar.Nav renders as a drawer and choosing an item closes it. Place Sidebar.Trigger in your page header to open it; it renders nothing from md up.

<Sidebar.Trigger>
  {({ onClick, ...attributes }) => (
    <Button
      variant="tertiary"
      size="sm"
      onlyIcon={SidebarIcon}
      ariaLabel="Open sidebar"
      onClick={onClick}
      attributes={attributes}
    />
  )}
</Sidebar.Trigger>

Use render on Sidebar.Item to render your router's link.

import NextLink from "next/link";

<Sidebar.Item
  href="/members"
  render={(attributes) => <NextLink {...attributes} href="/members" />}
>
  Members
</Sidebar.Item>

Use Breadcrumbs to show where a page sits in the hierarchy, and Tabs to switch between views of one page.

Accessibility

Key
Description
Tab
Moves focus through the items, skipping nested items while their parent is closed.
Enter
Follows a link, toggles a parent or group, or opens a menu.
Down arrowUp arrow
Moves through the items of an open rail or account menu.
Escape
Closes an open menu or the narrow screen drawer.
Set ariaLabel on Sidebar.Nav when the page has more than one navigation landmark.
Give the Sidebar.Trigger button an accessible name.