Sidebar App-shell sidebar components for full-page navigation layouts.
Sidebar provides the frame for full-page application shells: persistent
navigation, route-driven sidebar layers, a collapsible desktop rail, a mobile
sheet, an inset content region, and an optional secondary panel.
Uncontrolled sidebars follow a responsive default: open on wide desktop screens
and closed on narrower desktop screens. Users can toggle the sidebar within the
current breakpoint; crossing the breakpoint reapplies the responsive default.
Pass open to SidebarProvider when a surface needs to control that policy.
Open app shell example
The /examples shell keeps one SidebarProvider at the route boundary, renders
the primary navigation with SidebarLayers, reserves the rounded inset with
SidebarInsetFrame and SidebarInset, and renders the matter chat as a
secondary SidebarPanel.
import { Outlet, useLocation, useRouter } from "@tanstack/react-router" ;
import {
SidebarInset,
SidebarInsetBarLayer,
SidebarInsetBarLayers,
SidebarInsetFrame,
SidebarLayer,
SidebarLayers,
SidebarPanel,
SidebarProvider,
} from "@tilt-legal/cubitt-components/sidebar" ;
export function ExamplesShell () {
const location = useLocation ();
const router = useRouter ();
const isSettingsLayer = location.pathname. startsWith ( "/examples/settings" );
const hasChatTabs = location.pathname. startsWith ( "/examples/chat" );
return (
< SidebarProvider >
< SidebarLayers rail variant = "inset" >
< SidebarLayer id = "main" >
< AppSidebar />
</ SidebarLayer >
< SidebarLayer
active = {isSettingsLayer}
id = "settings"
onBack = {() => router. navigate ({ to: "/examples" })}
>
< SettingsSidebar />
</ SidebarLayer >
</ SidebarLayers >
< SidebarInsetFrame >
< SidebarInsetBarLayers >
< SidebarInsetBarLayer active = {hasChatTabs} id = "chat-tabs" >
< ChatTabs />
</ SidebarInsetBarLayer >
</ SidebarInsetBarLayers >
< SidebarInset >
< Outlet />
</ SidebarInset >
</ SidebarInsetFrame >
< SidebarPanel aria-label = "Matter chat" rail >
< MatterChat />
</ SidebarPanel >
</ SidebarProvider >
);
}
Use SidebarLayers when route state should swap the sidebar contents inside one
stable sidebar shell. Each route-specific sidebar component renders the normal
inner components, but it does not render another Sidebar.
import {
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarLayerBackButton,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "@tilt-legal/cubitt-components/sidebar" ;
export function SettingsSidebar () {
return (
< SidebarContent >
< SidebarGroup >
< SidebarGroupContent >
< SidebarMenu >
< SidebarMenuItem >
< SidebarLayerBackButton >Back to app</ SidebarLayerBackButton >
</ SidebarMenuItem >
</ SidebarMenu >
</ SidebarGroupContent >
</ SidebarGroup >
< SidebarGroup >
< SidebarGroupLabel >Workspace</ SidebarGroupLabel >
< SidebarGroupContent >
< SidebarMenu >
< SidebarMenuItem >
< SidebarMenuButton render = {< a href = "/examples/settings" />}>
< span >Members</ span >
</ SidebarMenuButton >
</ SidebarMenuItem >
</ SidebarMenu >
</ SidebarGroupContent >
</ SidebarGroup >
</ SidebarContent >
);
}
Set rail on Sidebar or SidebarLayers to keep a closed-edge target. Hovering
the rail previews the closed sidebar as an overlay without changing layout.
Clicking the rail toggles the real open state and shifts the layout.
Panels use the same model. The examples app keeps the chat trigger in the inset
header and lets SidebarPanel own the rail hover/click behavior.
import {
SidebarPanel,
SidebarPanelTrigger,
} from "@tilt-legal/cubitt-components/sidebar" ;
import { Xmark } from "@tilt-legal/cubitt-icons/ui/outline" ;
export function MatterChatPanel ({ enabled } : { enabled : boolean }) {
return (
< SidebarPanel
aria-label = "Matter chat"
open = {enabled ? undefined : false }
rail = {enabled}
>
< header >
< SidebarPanelTrigger aria-label = "Close matter chat" variant = "ghost" >
< Xmark />
</ SidebarPanelTrigger >
</ header >
< MatterChat />
</ SidebarPanel >
);
}
Use useSidebar when the trigger needs app-specific rendering. The examples app
uses a FeedbackButton so the primary sidebar toggle can move between the
sidebar header and the inset header while still using Cubitt's sidebar state.
import { FeedbackButton } from "@tilt-legal/cubitt-components/feedback-button" ;
import { useSidebar } from "@tilt-legal/cubitt-components/sidebar" ;
import { LayoutLeft, SidebarLeft } from "@tilt-legal/cubitt-icons/ui/outline" ;
export function ExamplesMainSidebarTrigger () {
const { open , toggleSidebar } = useSidebar ();
return (
< FeedbackButton
aria-label = {open ? "Close sidebar" : "Open sidebar" }
aria-expanded = {open}
controlled
defaultIcon = {< LayoutLeft />}
feedbackIcon = {< SidebarLeft />}
mode = "icon"
onFeedback = {toggleSidebar}
showFeedback = {open}
variant = "ghost"
/>
);
}
Sidebar content is composed from small layout components. In the examples app the
main sidebar uses a header, content groups, menu items, badges, and a footer.
import {
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuBadge,
SidebarMenuButton,
SidebarMenuItem,
} from "@tilt-legal/cubitt-components/sidebar" ;
export function AppSidebar () {
return (
<>
< SidebarHeader >
< SidebarMenu >
< SidebarMenuItem >
< SidebarMenuButton isActive >
< span >Home</ span >
</ SidebarMenuButton >
</ SidebarMenuItem >
</ SidebarMenu >
</ SidebarHeader >
< SidebarContent >
< SidebarGroup >
< SidebarGroupLabel >Matters</ SidebarGroupLabel >
< SidebarMenu >
< SidebarMenuItem >
< SidebarMenuButton >
< span >Acme Corp Investigation</ span >
</ SidebarMenuButton >
< SidebarMenuBadge >4</ SidebarMenuBadge >
</ SidebarMenuItem >
</ SidebarMenu >
</ SidebarGroup >
</ SidebarContent >
< SidebarFooter />
</>
);
}
Wraps the app shell and owns primary sidebar, mobile sidebar, panel, and rail
preview state.
Prop Type Default Description defaultOpenbooleanresponsive default Initial uncontrolled desktop sidebar state. openboolean- Controlled desktop sidebar state. onOpenChange(open: boolean) => void- Called when the desktop sidebar state changes. defaultPanelOpenbooleanfalseInitial uncontrolled secondary panel state. panelOpenboolean- Controlled secondary panel state. onPanelOpenChange(open: boolean) => void- Called when the secondary panel state changes. childrenReact.ReactNode- Sidebar, inset, and panel children. classNamestring- Additional classes for the shell wrapper. styleReact.CSSProperties- Additional wrapper style, merged with sidebar CSS variables. ...propsReact.ComponentProps<"div">- All other div attributes.
Sidebar renders one sidebar shell. SidebarLayers accepts the same props and
renders route-driven SidebarLayer children inside that shell.
Prop Type Default Description side"left" | "right""left"Viewport side for the sidebar. variant"sidebar" | "floating" | "inset""sidebar"Layout treatment for the sidebar and content inset. collapsible"offcanvas" | "icon" | "none""offcanvas"Collapse behavior. motion"push" | "reveal""reveal"Desktop open/close motion model. railbooleanfalseEnables the closed-edge hover/click rail. childrenReact.ReactNode- Sidebar content or SidebarLayer children. classNamestring- Additional classes for the sidebar container. ...propsReact.ComponentProps<"div">- All other div attributes.
Prop Type Default Description activebooleanfalseMarks this layer as the visible route layer. onBack() => void- Back handler read by SidebarLayerBackButton. childrenReact.ReactNode- Layer content. classNamestring- Additional classes for the rendered layer wrapper. idstring- Optional DOM id and layer context id. ...propsReact.ComponentProps<"div">- All other div attributes.
Prop Type Default Description childrenReact.ReactNode"Back"Button label. onClickReact.MouseEventHandler<HTMLButtonElement>- Runs before the layer onBack; prevent default to skip onBack. classNamestring- Additional classes. ...propsReact.ComponentProps<typeof SidebarMenuButton>- All other sidebar menu button props.
Prop Type Default Description childrenReact.ReactNode- Must contain exactly one SidebarInset; children before it render as top bars and children after it render as bottom bars. classNamestring- Additional classes for the frame. ...propsReact.ComponentProps<"div">- All other div attributes.
Prop Type Default Description childrenReact.ReactNode- Main page content. classNamestring- Additional classes for the inset surface. ...propsReact.ComponentProps<"main">- All other main attributes.
Prop Type Default Description childrenReact.ReactNode- SidebarInsetBarLayer children.classNamestring- Additional classes for the animated bar region. ...propsReact.ComponentProps<"div">- All other div attributes.
Prop Type Default Description activebooleanfalseMarks this bar layer as visible. childrenReact.ReactNode- Bar content. classNamestring- Additional classes for the rendered layer wrapper. idstring- Optional DOM id. ...propsReact.ComponentProps<"div">- All other div attributes.
Prop Type Default Description side"left" | "right""right"Viewport side for the secondary panel. openbooleanprovider panelOpen Controlled panel state. widthstringinternal panel width CSS width for the panel. motion"push" | "reveal""reveal"Panel open/close motion model. railbooleanfalseEnables the closed-edge panel rail. contentClassNamestring- Additional classes for the panel content surface. childrenReact.ReactNode- Panel content. classNamestring- Additional classes for the panel wrapper. styleReact.CSSProperties- Additional wrapper style, merged with --sidebar-panel-width. ...propsReact.ComponentProps<"aside">- All other aside attributes.
Prop Type Default Description openbooleanprovider panelOpen Controlled trigger state. onOpenChange(open: boolean) => void- Called with the next panel state. onClickReact.MouseEventHandler<HTMLButtonElement>- Runs before the panel state changes. classNamestring- Additional classes. ...propsReact.ComponentProps<typeof Button>- All other Cubitt button props.
Prop Type Default Description childrenReact.ReactNode<LayoutLeft />Trigger icon/content. onClickReact.MouseEventHandler<HTMLButtonElement>- Runs before the sidebar state toggles; prevent default to skip the toggle. classNamestring- Additional classes. ...propsReact.ComponentProps<typeof Button>- All other Cubitt button props.
Prop Type Default Description side"left" | "right""left"Viewport side for the rail. onClickReact.MouseEventHandler<HTMLButtonElement>- Runs before the sidebar toggles; prevent default to skip the toggle. classNamestring- Additional classes. ...propsReact.ComponentProps<"button">- All other button attributes.
Prop Type Default Description classNamestring- Additional classes. ...propsReact.ComponentProps<typeof Input>- All other Cubitt input props.
Prop Type Default Description childrenReact.ReactNode- Section content. classNamestring- Additional classes. ...propsReact.ComponentProps<"div">- All other div attributes.
Prop Type Default Description renderReact.ReactElement- Element to render instead of the default div. childrenReact.ReactNode- Label content. classNamestring- Additional classes. ...propsReact.ComponentProps<"div">- All other div attributes.
Prop Type Default Description renderReact.ReactElement- Element to render instead of the default button. childrenReact.ReactNode- Action content. classNamestring- Additional classes. ...propsReact.ComponentProps<"button">- All other button attributes.
Prop Type Default Description childrenReact.ReactNode- Menu items. classNamestring- Additional classes. ...propsReact.ComponentProps<"ul">- All other ul attributes.
Prop Type Default Description childrenReact.ReactNode- Menu item content. classNamestring- Additional classes. ...propsReact.ComponentProps<"li">- All other li attributes.
Prop Type Default Description renderReact.ReactElement- Element to render instead of the default button, such as an anchor or router link. isActivebooleanfalseApplies active item styling. variant"default" | "outline""default"Button visual treatment. size"default" | "sm" | "lg""default"Button size. childrenReact.ReactNode- Button content. classNamestring- Additional classes. ...propsReact.ComponentProps<"button">- All other button attributes.
Prop Type Default Description renderReact.ReactElement- Element to render instead of the default button. showOnHoverbooleanfalseHides the action until the menu item is hovered or focused on desktop. childrenReact.ReactNode- Action content. classNamestring- Additional classes. ...propsReact.ComponentProps<"button">- All other button attributes.
Prop Type Default Description childrenReact.ReactNode- Badge content. classNamestring- Additional classes. ...propsReact.ComponentProps<"div">- All other div attributes.
Prop Type Default Description showIconbooleanfalseRenders a leading icon skeleton. classNamestring- Additional classes. ...propsReact.ComponentProps<"div">- All other div attributes.
Prop Type Default Description childrenReact.ReactNode- Sub-menu items. classNamestring- Additional classes. ...propsReact.ComponentProps<"ul">- All other ul attributes.
Prop Type Default Description childrenReact.ReactNode- Sub-menu item content. classNamestring- Additional classes. ...propsReact.ComponentProps<"li">- All other li attributes.
Prop Type Default Description renderReact.ReactElement- Element to render instead of the default anchor. size"sm" | "md""md"Button size. isActivebooleanfalseApplies active item styling. childrenReact.ReactNode- Button content. classNamestring- Additional classes. ...propsReact.ComponentProps<"a">- All other anchor attributes.
Prop Type Default Description classNamestring- Additional classes. ...propsReact.ComponentProps<typeof Separator>- All other Cubitt separator props.
Return Type Description state"expanded" | "collapsed"Derived desktop sidebar state. openbooleanCurrent desktop sidebar state. setOpen(open: boolean) => voidSets the desktop sidebar state. openMobilebooleanCurrent mobile sheet state. setOpenMobile(open: boolean) => voidSets the mobile sheet state. panelOpenbooleanCurrent secondary panel state. setPanelOpen(open: boolean) => voidSets the secondary panel state. toggleSidebar() => voidToggles desktop sidebar or mobile sheet depending on breakpoint. togglePanel() => voidToggles the secondary panel. isMobilebooleanWhether the sidebar is currently using the mobile sheet behavior. sidebarPreviewActivebooleanWhether the primary sidebar rail overlay is mounted. setSidebarPreviewActive(active: boolean) => voidSets primary rail overlay mount state. sidebarPreviewOpenbooleanWhether the primary sidebar rail overlay is open. setSidebarPreviewOpen(open: boolean) => voidSets primary rail overlay visibility. panelPreviewActivebooleanWhether the panel rail overlay is mounted. setPanelPreviewActive(active: boolean) => voidSets panel rail overlay mount state. panelPreviewOpenbooleanWhether the panel rail overlay is open. setPanelPreviewOpen(open: boolean) => voidSets panel rail overlay visibility.
Return Type Description idstring | undefinedCurrent layer id. indexnumberCurrent layer index. activebooleanWhether this layer is the visible layer. visiblebooleanAlias for visible layer state. defaultLayerbooleanWhether this is the first layer. onBack(() => void) | undefinedBack handler supplied to the layer.