Sidebar
Compose an admin sidebar from explicit header, content, footer, navigation, and account-menu primitives.
The sidebar is composed from layout slots and path navigation primitives. The shell owns placement and collapse behavior; your application decides what belongs in the header, content, and footer.
Import sidebar components from @tulip-systems/app/components/client.
import {
Path,
PathGroup,
PathGroupItems,
PathLink,
Sidebar,
SidebarContent,
SidebarFooter,
SidebarHeader,
SidebarRail,
} from "@tulip-systems/app/components/client";Provider
Sidebar and its navigation primitives must render inside SidebarProvider. For an admin area, use
AdminLayout; it provides the sidebar state and pairs the sidebar with AdminContent.
import { AdminContent } from "@tulip-systems/app/components/client";
import { AdminLayout } from "@tulip-systems/app/components/server";
import { AdminSidebar } from "@/lib/config/admin-sidebar";
export default function AdminLayoutRoute(props: { children: React.ReactNode }) {
return (
<AdminLayout>
<AdminSidebar />
<AdminContent>{props.children}</AdminContent>
</AdminLayout>
);
}If the sidebar is used outside an admin layout, wrap it in SidebarProvider directly.
import { Sidebar, SidebarProvider } from "@tulip-systems/app/components/client";
export function AppSidebar() {
return (
<SidebarProvider>
<Sidebar>{/* header, content, footer, and rail */}</Sidebar>
</SidebarProvider>
);
}Basic sidebar
The standard composition has four slots:
SidebarHeader: static or account-related content at the top.SidebarContent: the scrollable navigation area.SidebarFooter: optional persistent actions at the bottom.SidebarRail: optional desktop collapse affordance.
import { LayoutDashboardIcon, SettingsIcon } from "lucide-react";
import {
DefaultSidebarAccountMenu,
Path,
PathGroup,
PathGroupItems,
PathLink,
Sidebar,
SidebarContent,
SidebarFooter,
SidebarHeader,
SidebarMenu,
SidebarRail,
} from "@tulip-systems/app/components/client";
export function AdminSidebar() {
return (
<Sidebar>
<SidebarHeader>
<DefaultSidebarAccountMenu />
</SidebarHeader>
<SidebarContent>
<PathGroup>
<PathGroupItems>
<Path segment="(dashboard)">
<PathLink href="/admin">
<LayoutDashboardIcon />
Dashboard
</PathLink>
</Path>
</PathGroupItems>
</PathGroup>
</SidebarContent>
<SidebarFooter>
<SidebarMenu>
<Path segment="settings">
<PathLink href="/admin/settings">
<SettingsIcon />
Settings
</PathLink>
</Path>
</SidebarMenu>
</SidebarFooter>
<SidebarRail />
</Sidebar>
);
}SidebarRail is deliberately explicit. Omit it when the sidebar should not have a clickable desktop
collapse rail, such as when a top-bar trigger is the only collapse control.
Path navigation
Use PathGroup and PathGroupItems to create a top-level menu list. A Path receives its active
state from the first selected Next.js layout segment. Its PathLink automatically closes the sidebar
after navigation on mobile.
import { ListTodoIcon, Table2Icon } from "lucide-react";
import {
Path,
PathGroup,
PathGroupItems,
PathGroupTitle,
PathLink,
} from "@tulip-systems/app/components/client";
<PathGroup permission={[{ project: ["view"] }, { task: ["view"] }]}>
<PathGroupTitle>Projects</PathGroupTitle>
<PathGroupItems>
<Path permission={{ project: ["view"] }} segment="projects">
<PathLink href="/admin/projects">
<Table2Icon />
Projects
</PathLink>
</Path>
<Path permission={{ task: ["view"] }} segment="tasks">
<PathLink href="/admin/tasks">
<ListTodoIcon />
My Tasks
</PathLink>
</Path>
</PathGroupItems>
</PathGroup>PathGroup.permission uses an any strategy, so the group appears when the user has at least one
permission. Path.permission hides an individual item when its permission is unavailable.
Nested paths
Put PathItems inside a Path for second-level navigation. It only renders while its parent path is
active. PathSubItem matches the second selected layout segment.
import { Table2Icon } from "lucide-react";
import {
Path,
PathItems,
PathLink,
PathSubItem,
PathSubItemLink,
} from "@tulip-systems/app/components/client";
<Path permission={{ project: ["view"] }} segment="projects">
<PathLink href="/admin/projects">
<Table2Icon />
Projects
</PathLink>
<PathItems>
<PathSubItem permission={{ project: ["view.drive"] }} segment="drive">
<PathSubItemLink href="/admin/projects/drive">Drive</PathSubItemLink>
</PathSubItem>
<PathSubItem segment="timeline">
<PathSubItemLink href="/admin/projects/timeline">Timeline</PathSubItemLink>
</PathSubItem>
</PathItems>
</Path>Use the optional isActive prop on Path or PathSubItem when the active state cannot be inferred
from the route segment.
Account menu
DefaultSidebarAccountMenu provides the standard user trigger, settings link, theme chooser, and
sign-out action. It requires the app's AuthProvider and theme provider to be present.
For a custom menu, compose the account-menu primitives in the header. The root component owns only the dropdown state and shared close behavior; the trigger reads the session user and the logout item owns its sign-out behavior.
import { SettingsIcon, UserIcon } from "lucide-react";
import {
DropdownMenuItem,
SidebarAccountLogoutItem,
SidebarAccountMenu,
SidebarAccountMenuContent,
SidebarAccountMenuItem,
SidebarAccountMenuLink,
SidebarAccountMenuTrigger,
SidebarAccountThemeMenu,
SidebarHeader,
DropdownMenuSeparator,
useSidebarAccountMenu,
} from "@tulip-systems/app/components/client";
export function AdminSidebarHeader() {
return (
<SidebarHeader>
<SidebarAccountMenu>
<SidebarAccountMenuTrigger />
<SidebarAccountMenuContent>
<SidebarAccountMenuLink href="/admin/profile">
<UserIcon />
Profile
</SidebarAccountMenuLink>
<SidebarAccountMenuLink href="/admin/settings">
<SettingsIcon />
Settings
</SidebarAccountMenuLink>
<DropdownMenuSeparator />
<SidebarAccountThemeMenu />
<SidebarAccountLogoutItem />
</SidebarAccountMenuContent>
</SidebarAccountMenu>
</SidebarHeader>
);
}To add a non-navigation action, use SidebarAccountMenuItem. It closes the dropdown and mobile
sidebar after its click handler runs.
function FeedbackItem() {
return (
<SidebarAccountMenuItem onClick={() => openFeedbackDialog()}>
Send feedback
</SidebarAccountMenuItem>
);
}For a custom action that needs to close the menu at a different point, call useSidebarAccountMenu.
function CustomAccountItem() {
const { close } = useSidebarAccountMenu();
return (
<DropdownMenuItem
onClick={() => {
close();
openWorkspaceSwitcher();
}}
>
Switch workspace
</DropdownMenuItem>
);
}Primitives
| Component | Responsibility |
|---|---|
Sidebar | Sidebar shell, placement, responsive sheet behavior, and collapse variant. |
SidebarHeader | Top layout slot. |
SidebarContent | Scrollable main layout slot. |
SidebarFooter | Bottom layout slot for persistent actions. |
SidebarRail | Optional desktop collapse rail. |
PathGroup | Permission-aware navigation section. |
Path / PathLink | Top-level active route item and link. |
PathItems | Conditional nested-navigation list for the active parent path. |
PathSubItem / PathSubItemLink | Second-level active route item and link. |
SidebarAccountMenu | Account dropdown root and shared close behavior. |
DefaultSidebarAccountMenu | Ready-made account menu with settings, theme, and logout. |