Sidebar

App-shell sidebar components for full-page navigation layouts.

Overview

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

Shell Layout

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>
  );
}

Rails And Panels

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>
  );
}

Custom Triggers

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 />
    </>
  );
}

API Reference

SidebarProvider

Wraps the app shell and owns primary sidebar, mobile sidebar, panel, and rail preview state.

PropTypeDefaultDescription
defaultOpenbooleanresponsive defaultInitial 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.

PropTypeDefaultDescription
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.

SidebarLayer

PropTypeDefaultDescription
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.

SidebarLayerBackButton

PropTypeDefaultDescription
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.

SidebarInsetFrame

PropTypeDefaultDescription
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.

SidebarInset

PropTypeDefaultDescription
childrenReact.ReactNode-Main page content.
classNamestring-Additional classes for the inset surface.
...propsReact.ComponentProps<"main">-All other main attributes.

SidebarInsetBarLayers

PropTypeDefaultDescription
childrenReact.ReactNode-SidebarInsetBarLayer children.
classNamestring-Additional classes for the animated bar region.
...propsReact.ComponentProps<"div">-All other div attributes.

SidebarInsetBarLayer

PropTypeDefaultDescription
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.

SidebarPanel

PropTypeDefaultDescription
side"left" | "right""right"Viewport side for the secondary panel.
openbooleanprovider panelOpenControlled panel state.
widthstringinternal panel widthCSS 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.

SidebarPanelTrigger

PropTypeDefaultDescription
openbooleanprovider panelOpenControlled 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.

SidebarTrigger

PropTypeDefaultDescription
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.

SidebarRail

PropTypeDefaultDescription
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.

SidebarInput

PropTypeDefaultDescription
classNamestring-Additional classes.
...propsReact.ComponentProps<typeof Input>-All other Cubitt input props.

SidebarHeader, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent

PropTypeDefaultDescription
childrenReact.ReactNode-Section content.
classNamestring-Additional classes.
...propsReact.ComponentProps<"div">-All other div attributes.

SidebarGroupLabel

PropTypeDefaultDescription
renderReact.ReactElement-Element to render instead of the default div.
childrenReact.ReactNode-Label content.
classNamestring-Additional classes.
...propsReact.ComponentProps<"div">-All other div attributes.

SidebarGroupAction

PropTypeDefaultDescription
renderReact.ReactElement-Element to render instead of the default button.
childrenReact.ReactNode-Action content.
classNamestring-Additional classes.
...propsReact.ComponentProps<"button">-All other button attributes.

SidebarMenu

PropTypeDefaultDescription
childrenReact.ReactNode-Menu items.
classNamestring-Additional classes.
...propsReact.ComponentProps<"ul">-All other ul attributes.

SidebarMenuItem

PropTypeDefaultDescription
childrenReact.ReactNode-Menu item content.
classNamestring-Additional classes.
...propsReact.ComponentProps<"li">-All other li attributes.

SidebarMenuButton

PropTypeDefaultDescription
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.

SidebarMenuAction

PropTypeDefaultDescription
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.

SidebarMenuBadge

PropTypeDefaultDescription
childrenReact.ReactNode-Badge content.
classNamestring-Additional classes.
...propsReact.ComponentProps<"div">-All other div attributes.

SidebarMenuSkeleton

PropTypeDefaultDescription
showIconbooleanfalseRenders a leading icon skeleton.
classNamestring-Additional classes.
...propsReact.ComponentProps<"div">-All other div attributes.

SidebarMenuSub

PropTypeDefaultDescription
childrenReact.ReactNode-Sub-menu items.
classNamestring-Additional classes.
...propsReact.ComponentProps<"ul">-All other ul attributes.

SidebarMenuSubItem

PropTypeDefaultDescription
childrenReact.ReactNode-Sub-menu item content.
classNamestring-Additional classes.
...propsReact.ComponentProps<"li">-All other li attributes.

SidebarMenuSubButton

PropTypeDefaultDescription
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.

SidebarSeparator

PropTypeDefaultDescription
classNamestring-Additional classes.
...propsReact.ComponentProps<typeof Separator>-All other Cubitt separator props.

useSidebar

ReturnTypeDescription
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.

useSidebarLayer

ReturnTypeDescription
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.

On this page