Skip to main content
Urbicon UI

Sidebar

Sidebar primitive — fixed-position panel, permanent on desktop and overlay on mobile. Use directly for detail panels and custom shells. For a standard application chrome (left rail + mobile hamburger), use SidebarLayout.

Overview

Looking for an app shell?

For the common pattern of a permanent left sidebar with a mobile hamburger and an offset main content area, prefer SidebarLayout. It wraps this primitive and resolves the CSS-variable scoping that otherwise leaves your main content underneath the sidebar.

Use the Sidebar primitive directly for right-side detail panels, custom shells, or any sidebar that opens as an overlay on click.

Playground

Side
<script lang="ts">
  import { Sidebar, Button } from '@urbicon-ui/blocks';

  let open = $state(false);
</script>

<Sidebar
  bind:open
>
  {#snippet header()}
    <div class="flex items-center justify-between py-3">
      <span class="text-text-primary font-semibold">Navigation</span>
      <Button
        variant="ghost"
        intent="neutral"
        size="xs"
        onclick={() => (open = false)}
        aria-label="Close sidebar"
      >
        <CloseIcon class="h-4 w-4" />
      </Button>
    </div>
  {/snippet}
  <nav class="space-y-1 p-4">
    {#each ['Dashboard', 'Projects', 'Team', 'Settings'] as item (item)}
      <button
        class="text-text-secondary hover:bg-surface-hover hover:text-text-primary w-full rounded-lg px-3 py-2 text-left text-sm transition-colors"
        onclick={() => (open = false)}
      >
        {item}
      </button>
    {/each}
  </nav>
</Sidebar>

01 When to use

Sidebar is an <aside> landmark for a side panel that is part of the page layout. Use it for persistent app navigation that slides in as an overlay on mobile (mode="responsive") or width-collapses at all viewports (mode="collapsible"). On desktop there is no backdrop — the panel sits alongside the main content.

Pick a different overlay if you need:

  • A ready-made app shell with mobile hamburger, header slot, and centered content column → SidebarLayout.
  • A transient detail panel that pulls focus (backdrop + focus-trap, opens on click, closes after action) → Drawer.
  • A floating panel anchored to a specific element (date picker, action menu, autocomplete) → Popover.

02 Examples

Right-Side Detail Panel

The primary use case for the Sidebar primitive: a detail or property panel that slides in from an edge as an overlay on click.
<Sidebar bind:open side="right" width="20rem">
  {#snippet header()}
    <span class="font-semibold">Item Details</span>
  {/snippet}
  <div class="space-y-4 p-4">
    <div>
      <dt class="text-xs text-text-tertiary">Name</dt>
      <dd class="text-sm text-text-primary">Project Alpha</dd>
    </div>
    <div>
      <dt class="text-xs text-text-tertiary">Status</dt>
      <dd><Badge intent="success" size="sm">Active</Badge></dd>
    </div>
  </div>
</Sidebar>

Collapsible App Navigation

The second of the two modes, and the one the Playground cannot reach. In responsive mode (the default) open only drives the mobile overlay — on a desktop width the panel is always there. In collapsible mode open drives it at every width: the panel stays mounted and animates its width to 0 instead of sliding away, which is why the button below toggles rather than opens. Keep it mounted — wrapping it in a conditional block gets you a disappearing panel in either mode and no animation in this one.
<!-- The panel stays mounted; open animates its width, it does not unmount. -->
<Button variant="outlined" onclick={() => (navOpen = !navOpen)}>
  {navOpen ? 'Collapse' : 'Expand'} navigation
</Button>

<Sidebar bind:open={navOpen} mode="collapsible" width="16rem">
  {#snippet header()}
    <span class="font-semibold">Acme</span>
  {/snippet}
  <nav class="space-y-1 p-3">
    {#each ['Overview', 'Analytics', 'Settings'] as item (item)}
      <a href="/{item.toLowerCase()}" class="block rounded-lg px-3 py-2 text-sm">{item}</a>
    {/each}
  </nav>
</Sidebar>

03 Customization

Branded Sidebar

Use slotClasses to create a dark-themed branded sidebar.
<Sidebar
  bind:open
  slotClasses={{
    panel: 'bg-neutral-900 border-neutral-800',
    header: 'border-neutral-800',
    footer: 'border-neutral-800'
  }}
>
  {#snippet header()}
    <span class="text-white font-semibold">Brand</span>
  {/snippet}
  <nav class="p-3">
    <button class="text-neutral-400 hover:bg-neutral-800">Overview</button>
  </nav>
</Sidebar>

Sidebar also supports unstyled for a fully hand-rolled shell. The branded dark panel above is natural preset material: register it under presets.Sidebar on BlocksProvider and apply it via preset — see Customization.

04 Accessibility

Semantic Landmark

Renders as an <aside> element, which screen readers announce as a complementary landmark. When the sidebar is closed (mobile overlay dismissed, or collapsible mode closed), it receives both aria-hidden="true" and inert — the panel keeps its children mounted, so removing it from the accessibility tree without also removing it from the tab order would let a keyboard user walk into a region their screen reader skips. Focus that was inside the panel returns to whatever held it before the panel opened.

Keyboard

Escape closes the mobile overlay (configurable via closeOnEscape). Backdrop click dismiss is configurable via closeOnBackdropClick.

Responsive Behavior

In responsive mode (default): on desktop (≥1024px) the sidebar is always visible as a fixed panel — the open prop only controls the mobile overlay. Below 1024px it slides in as an overlay with backdrop and body scroll lock.

In collapsible mode: the open prop controls the sidebar at all viewports. On desktop it animates its width (no backdrop or scroll lock). On mobile it behaves as an overlay like responsive mode. Use --sidebar-effective-width (0px when closed, full width when open) to transition your main content offset.

Reduced Motion

Slide and width transitions use CSS custom property durations that respect prefers-reduced-motion via the design token system.

05 API Reference

15 props
15 props
Prop
Type
Default
Description

06 Types

Local type definitions used by this component.

3 types
Name
Kind
Category
Used by
Description

07 Installation

Import

import { Sidebar } from '@urbicon-ui/blocks';