SidebarLayoutcomponent

SidebarLayout({ sidebar, children, right = false }: SidebarLayoutProps): ReactElement
ParamType
sidebarSidebarLayoutProps
The side-column content, rendered inside a <nav>. required
    .sidebarReactNode
Content rendered in the fixed-width side column. required
    .rightboolean
Render the sidebar on the right rather than the left.
Return
ReactElement
The sidebar layout element.

Layout with a fixed-width side column (typically navigation) next to a scrollable main content column.

  • The sidebar is rendered as <nav> — it almost always contains the page's primary navigation.
  • On narrow viewports the sidebar becomes an off-canvas drawer toggled by a single menu button that switches between a burger and a close icon.
  • While the drawer is open an overlay dims the rest of the page; clicking the overlay closes the drawer.
  • Inside a <Navigation> the drawer closes itself whenever the route changes (e.g. tapping a sidebar link).
  • The scrollable content column is kept alive across navigation via <RouteCache>, so returning to a recently-visited page restores its scroll position and state; the sidebar stays mounted throughout.
  • Use the --sidebar-layout-width, --sidebar-layout-bg, --sidebar-layout-border, and --sidebar-layout-color-border custom properties to override defaults.

A full-viewport layout with a fixed-width side column next to a scrollable main content column. The sidebar renders as a <nav> landmark — it almost always holds the primary navigation. On narrow viewports it collapses to an off-canvas drawer toggled by a single burger/close button.

Things to know:

  • Pass right to place the sidebar on the right rather than the left.
  • The sidebar renders as <nav>, so it is a navigation landmark without extra markup — drop a <Menu> inside it.
  • While the drawer is open an overlay dims the page; clicking the overlay closes it.
  • Inside a <Navigation> context the drawer closes itself whenever the route changes (e.g. tapping a sidebar link).
  • The layout owns scroll, padding, and safe-area insets so individual pages don't have to.

Usage

tsx
import { SidebarLayout, Menu, MenuItem, Router } from "shelving/ui";

function AppShell() {
  const nav = (
    <Menu>
      <MenuItem href="/dashboard">Dashboard</MenuItem>
      <MenuItem href="/users">Users</MenuItem>
      <MenuItem href="/settings">Settings</MenuItem>
    </Menu>
  );
  return (
    <SidebarLayout sidebar={nav}>
      <Router routes={ROUTES}/>
    </SidebarLayout>
  );
}

Layouts compose naturally as <Router> route values — wrap a group of routes in a shared layout, then route further inside it.

Keyboard-aware safe area

useSafeKeyboardArea() (exported alongside the layouts) tracks the dynamic viewport and writes a --layout-inset-bottom custom property reflecting the space hidden behind the on-screen keyboard. This is an iOS Safari workaround until interactive-widget viewport support lands.

tsx
import { useSafeKeyboardArea } from "shelving/ui";

useEffect(useSafeKeyboardArea, []);

Styling

VariableStylesDefault
--sidebar-layout-widthWidth of the side column (and drawer)17.5rem
--sidebar-layout-backgroundPage background while the layout is mountedvar(--tint-100)
--sidebar-layout-sidebar-backgroundSidebar column fillvar(--tint-90)
--sidebar-layout-content-backgroundMain content column fillvar(--tint-100)
--sidebar-layout-borderDivider between sidebar and contentvar(--stroke-normal) solid var(--tint-80)

The sidebar and content columns own their own scroll behaviour directly (this layout no longer composes a shared .layout class). useSafeKeyboardArea() still writes --layout-inset-bottom for layouts that pad to the safe area.

Global tokens it reads — the tint ladder --tint-80 / --tint-90 / --tint-100, plus --space-normal, --stroke-normal, --duration-normal, and --color-shadow.

Examples

<SidebarLayout sidebar={<Menu />}><Page /></SidebarLayout>