ComponentsSidebar
Sidebar
Organize app navigation in a sidebar that can collapse.
On this page
Install component source from the public registry, or use the local workspace package. Follow the setup guide first. The npm package is not published yet.
View registry files- Motion for React
(motion)Animations, transitions, and reduced-motion handling.
- React DOM
(react-dom)Portals for rendering overlay content outside the normal component tree.
Shared foundation: React, clsx for conditional classes, tailwind-merge for resolving utility-class conflicts. Styles use Tailwind CSS and Dethink CSS variables.
Based on this component’s source and registry composition. Libraries used only in showcase examples are excluded.
Import the component into your page or component file.
import {
Sidebar,
SidebarProvider,
SidebarMenuLink,
} from "@dethink/components";Try the examples, then open the code to use them in your app.
Background work
Keep AI runs, imports, and approval requests visible. Collapse the sidebar to turn the list into an activity summary.
Show sourceexamples/sidebar/activity.tsx
"use client";
import { useState, useSyncExternalStore } from "react";
import {
Button,
Sidebar,
SidebarActivity,
SidebarContent,
SidebarHeader,
SidebarProvider,
SidebarTrigger,
type SidebarActivityItem,
} from "@dethink/components";
import { Layers } from "lucide-react";
const subscribeHydration = () => () => {};
export function SidebarActivityExample() {
const hydrated = useSyncExternalStore(
subscribeHydration,
() => true,
() => false,
);
const [reviewed, setReviewed] = useState(false);
const [finished, setFinished] = useState(false);
const [empty, setEmpty] = useState(false);
const items: SidebarActivityItem[] = empty
? []
: [
{
id: "brief",
title: "Launch brief",
status: reviewed ? "complete" : "attention",
description: reviewed
? "Approved and ready to share"
: "Your approval is needed to continue",
action: reviewed
? undefined
: { label: "Approve brief", onAction: () => setReviewed(true) },
},
{
id: "index",
title: "Indexing documents",
status: finished ? "complete" : "running",
progress: 62,
description: finished
? "128 documents ready to search"
: "79 of 128 documents",
action: finished
? undefined
: { label: "Finish demo run", onAction: () => setFinished(true) },
},
{
id: "export",
title: "Weekly report",
status: "queued",
description: "Waiting for the index",
},
];
return (
<fieldset
disabled={!hydrated}
aria-label="Activity demo"
className="min-w-0 space-y-4"
data-testid="sidebar-activity-demo"
>
<div className="flex flex-wrap gap-2">
<Button size="sm" variant="outline" onClick={() => setEmpty(!empty)}>
{empty ? "Show activity" : "Clear activity"}
</Button>
<Button
size="sm"
variant="ghost"
onClick={() => {
setReviewed(false);
setFinished(false);
setEmpty(false);
}}
>
Reset demo
</Button>
</div>
<SidebarProvider motion="subtle" className="mx-auto max-w-64">
<Sidebar
className="h-[31rem] rounded-xl border"
aria-label="Activity workspace"
>
<SidebarHeader>
<div className="flex items-center gap-2 group-data-[collapsed=true]/sidebar:flex-col">
<span className="flex size-9.5 shrink-0 items-center justify-center">
<Layers
aria-hidden="true"
data-testid="activity-workspace-icon"
className="text-primary size-5 shrink-0"
/>
</span>
<span className="min-w-0 flex-1 truncate text-sm font-semibold group-data-[collapsed=true]/sidebar:hidden">
Studio workspace
</span>
<SidebarTrigger className="size-9.5" />
</div>
</SidebarHeader>
<SidebarContent>
<SidebarActivity items={items} label="Background work" />
</SidebarContent>
</Sidebar>
</SidebarProvider>
</fieldset>
);
}Dashboard navigation
A labelled nav landmark with grouped links, current route state, badges, shortcuts, a visible header toggle, and content inset.
Collapsed rail
Icon rail mode preserves accessible names while trading visual labels for workspace width.
Show sourceexamples/sidebar/collapsed.tsx
"use client";
import {
Sidebar,
SidebarContent,
SidebarHeader,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarInset,
SidebarMenu,
SidebarMenuItem,
SidebarMenuLink,
SidebarProvider,
SidebarTrigger,
} from "@dethink/components";
import { FileBarChart, FolderKanban, Inbox } from "lucide-react";
const collapsedItems = [
{ href: "/inbox", icon: Inbox, label: "Inbox" },
{ href: "/projects", icon: FolderKanban, label: "Projects" },
{ href: "/reports", icon: FileBarChart, label: "Reports" },
];
export function SidebarCollapsed() {
return (
<SidebarProvider defaultCollapsed motion="subtle">
<div className="border-border bg-background flex h-72 overflow-hidden rounded-lg border">
<Sidebar aria-label="Collapsed navigation" variant="bordered">
<SidebarHeader>
<SidebarTrigger />
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Rail</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{collapsedItems.map((item, index) => {
const Icon = item.icon;
return (
<SidebarMenuItem key={item.href}>
<SidebarMenuLink
current={index === 0}
href={item.href}
icon={<Icon aria-hidden="true" />}
>
{item.label}
</SidebarMenuLink>
</SidebarMenuItem>
);
})}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
<SidebarInset className="text-muted-foreground p-5 text-sm">
Collapsed labels remain in the accessibility tree while visual space
stays compact.
</SidebarInset>
</div>
</SidebarProvider>
);
}Mobile menu
A contained mobile navigation handoff with explicit open and close states for embedded workspace previews.
Examples that combine components for common tasks.
AI workspace navigation
A floating sidebar with nested workflow navigation, live badges, disabled items, motion preset state, and dashboard content.
Sidebar is composable. The provider owns shared state; parts render semantic navigation, controls, drawers, and app-shell companions.
| Prop | What it does | Default |
|---|---|---|
SidebarProvider{ animate, collapsed, defaultCollapsed, onCollapsedChange, mobileOpen, defaultMobileOpen, onMobileOpenChange, side, variant, motion } | Owns desktop collapsed state, mobile drawer state, side placement, visual variant, animation opt-out, and motion preset. | Not set |
Sidebarnav attributes + { side, variant } | The labelled navigation landmark. Side and variant default from the provider but can be overridden. | aria-label="Sidebar" |
SidebarMenuLink{ href, current, active, disabled, external, asChild, icon, description, badge, shortcut, tooltip } | Navigation item rendered as a real link by default, with aria-current and data-state hooks for current route styling. Current items render an animated selection indicator; collapsed items expose a CSS-only tooltip (string labels are used automatically, or pass tooltip), a badge dot, and an initial fallback when no icon is given. | Not set |
SidebarMenuAction{ label, showOnHover } | Icon-sized secondary action. With showOnHover it positions itself at the end of the parent SidebarMenuItem row and reveals on row hover, focus-within, or its own focus. | showOnHover: false |
SidebarSeparatordiv attributes | Decorative token-backed rule for dividing sidebar sections. | Not set |
SidebarGroup{ collapsible, open, defaultOpen, onOpenChange } | Groups dense navigation sections; collapsible groups pair with SidebarGroupTrigger and hide content when closed. | defaultOpen: true |
SidebarTrigger / SidebarRailbutton props | Desktop collapse controls with aria-expanded state. SidebarTrigger is the default 40px navigation-strokes and directional-chevron button: place it in the sidebar header or the shell header. SidebarRail is an optional edge alternative for existing consumers. Use one primary desktop toggle. | toggle collapse |
SidebarMobile / SidebarMobileTrigger{ label, closeButtonLabel, showCloseButton } / button props | Mobile drawer surface and trigger. The drawer uses the configured physical left/right edge in LTR and RTL, inherits text direction across its portal, and handles Escape, outside click, link activation close, and focus restore. | label: "Sidebar navigation" |
SidebarInset{ as?: "main" | "div" | "section" } | Lightweight content companion for app shells that need Sidebar and main content to share a flex row. | "main" |
animateboolean | Enables the CSS choreography and Motion edge-handle feedback. Set false to resolve every sidebar surface to motion none. | true |
motion"none" | "subtle" | "standard" | "expressive" | Coordinates CSS width, disclosure, drawer, and menu transitions with the Motion-powered edge handle. Reduced-motion preferences disable transform-heavy handle movement. | "standard" |