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>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.
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.
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.
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.
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.
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.
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>Custom Links
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
ariaLabel on Sidebar.Nav when the page has more than one navigation landmark.Sidebar.Trigger button an accessible name.