Skip to main content

Sidebar

Sidebar

Responsive sidebar navigation with collapsible sections

Tags: component layout navigation

View Demo

Sidebar provides responsive navigation with collapsible sections, search filtering, and role-based visibility.

Import​

<script>
  import { Sidebar } from '@classic-homes/theme-svelte';
</script>

Basic Usage​

<script>
  const navigation = [
    {
      id: 'main',
      title: 'Main',
      items: [
        { id: 'home', name: 'Home', href: '/', icon: 'home' },
        { id: 'projects', name: 'Projects', href: '/projects', icon: 'folder' },
      ],
    },
  ];
</script>

<Sidebar {navigation} />

Props​

Prop Type Default Description
navigation NavSection[] required Navigation sections
userRoles string[] - Every role the user holds
userPermissions string[] - Every permission the user holds
requireAllRoles boolean false Require ALL of an item's roles rather than any
requireAllPermissions boolean false Require ALL of an item's permissions rather than any
collapsed boolean false Desktop collapsed state
isMobile boolean false Mobile mode
mobileOpen boolean false Mobile drawer open
onClose () => void - Mobile close callback
onNavigate (item: NavItem) => void - Navigation callback
logo Snippet - Custom logo
icon Snippet<[NavItem]> - Custom icon renderer
quickLinks QuickLink[] - Quick links at bottom
quickLinksDisplay 'list' | 'icons' 'list' Quick links display
quickLinkIcon Snippet<[QuickLink]> - Quick link icon renderer
footer Snippet - Footer content
variant 'light' | 'dark' 'light' Visual variant
strongBorder boolean false Thicker border
expandedWidth number 256 Expanded width (px)
collapsedWidth number 64 Collapsed width (px)
searchable boolean false Enable search
autoCollapse boolean false Opening a section or item closes others at its level
searchPlaceholder string 'Search...' Search placeholder
class string - Additional classes
enablePinning boolean false Enable pinning feature for navigation items
pinnedSectionTitle string 'Pinned' Title for the pinned items section (default: "Pinned")
initialPinnedItems string[] - Initial pinned item IDs for server hydration
onPinnedChange (pinnedIds: string[]) => void - Callback when pinned items change (for external persistence)
pinIcon Snippet<[{ isPinned: boolean }]> - Custom pin icon renderer
appId string - App identifier for per-app preferences storage

Types​

interface NavSection {
  id: string;
  title?: string;
  items: NavItem[];
  collapsible?: boolean;
  expanded?: boolean;
}

interface NavItem {
  id: string;
  name: string;
  href?: string;
  icon?: string;
  active?: boolean;
  children?: NavItem[];
  expanded?: boolean;
  roles?: string[];
}

interface QuickLink {
  id: string;
  name: string;
  href: string;
  icon?: string;
  external?: boolean;
}

Sections with Titles​

<script>
  const navigation = [
    {
      id: 'main',
      title: 'Main',
      items: [
        { id: 'dashboard', name: 'Dashboard', href: '/dashboard' },
        { id: 'projects', name: 'Projects', href: '/projects' },
      ],
    },
    {
      id: 'settings',
      title: 'Settings',
      collapsible: true,
      items: [
        { id: 'profile', name: 'Profile', href: '/profile' },
        { id: 'account', name: 'Account', href: '/account' },
      ],
    },
  ];
</script>

Nested Navigation​

<script>
  const navigation = [
    {
      id: 'docs',
      items: [
        {
          id: 'components',
          name: 'Components',
          children: [
            { id: 'button', name: 'Button', href: '/components/button' },
            { id: 'input', name: 'Input', href: '/components/input' },
            { id: 'card', name: 'Card', href: '/components/card' },
          ],
        },
      ],
    },
  ];
</script>

Role-Based Visibility​

Filter navigation by the roles a user holds:

<script>
  const navigation = [
    {
      id: 'admin',
      title: 'Admin',
      items: [
        { id: 'users', name: 'Users', href: '/admin/users', roles: ['admin'] },
        { id: 'settings', name: 'Settings', href: '/admin/settings', roles: ['admin'] },
      ],
    },
  ];

  const userRoles = ['user', 'editor']; // Won't see admin items
</script>

<Sidebar {navigation} {userRoles} />

A user may hold several roles, and items are matched against the whole set — so a ['manager', 'agent'] user sees everything either role allows:

<Sidebar {navigation} userRoles={['manager', 'agent']} />

Matching is exact: an item requiring agent is hidden from an admin who does not literally hold agent. List every role that should see it (roles: ['admin', 'agent']) rather than relying on rank.

Nested children are filtered too, so a restricted child under an open parent stays hidden. A parent left with no visible children is dropped, unless it has its own href.

Nesting​

children nest to any depth: a child with its own children renders as an expandable row one level in. Search, pinning and the collapsed-mode flyout follow every level. Set autoCollapse for accordion behaviour, where opening a section or item closes the others at its level (Docusaurus autoCollapseCategories).

Permissions​

Items can gate on permissions instead of — or as well as — roles. An item declaring both requires the user to satisfy each:

<script>
  const navigation = [
    {
      id: 'main',
      title: 'Main',
      items: [
        { id: 'reports', name: 'Reports', href: '/reports', permissions: ['reports:read'] },
        {
          id: 'payouts',
          name: 'Payouts',
          href: '/payouts',
          roles: ['manager'],
          permissions: ['payouts:write'],
        },
      ],
    },
  ];
</script>

<Sidebar {navigation} userRoles={user.roles} userPermissions={user.permissions} />

By default any listed role or permission matches. Use requireAllRoles / requireAllPermissions to demand the full set.

Filtering decides what is rendered. It is not an access control boundary — the server enforces access, and hiding a link is a courtesy to the user rather than a security measure.

Search/Filter​

Enable navigation search:

<Sidebar {navigation} searchable searchPlaceholder="Filter navigation..." />

Dark Variant​

<Sidebar {navigation} variant="dark" />

Collapsed Mode​

Icons-only mode with flyout menus:

<Sidebar {navigation} collapsed />

Mobile Mode​

Drawer style for mobile:

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

<Sidebar {navigation} isMobile {mobileOpen} onClose={() => (mobileOpen = false)} />
<Sidebar {navigation}>
  {#snippet logo()}
    <img src="/logo.svg" alt="Logo" class="h-8" />
  {/snippet}
</Sidebar>

Custom Icons​

<Sidebar {navigation}>
  {#snippet icon(item)}
    <Icon name={item.icon} class="h-5 w-5" />
  {/snippet}
</Sidebar>

Add quick access links at the bottom:

<script>
  const quickLinks = [
    { id: 'docs', name: 'Documentation', href: '/docs', icon: 'book' },
    { id: 'support', name: 'Support', href: '/support', icon: 'help' },
    { id: 'feedback', name: 'Feedback', href: '/feedback', icon: 'message', external: true },
  ];
</script>

<Sidebar {navigation} {quickLinks} />
<Sidebar {navigation}>
  {#snippet footer()}
    <div class="flex items-center gap-3">
      <Avatar size="sm">JD</Avatar>
      <div>
        <p class="text-sm font-medium">John Doe</p>
        <p class="text-xs text-muted-foreground">Admin</p>
      </div>
    </div>
  {/snippet}
</Sidebar>

Handle navigation events:

<Sidebar
  {navigation}
  onNavigate={(item) => {
    console.log('Navigated to:', item.href);
    trackEvent('navigation', { page: item.name });
  }}
/>

Examples​

Dashboard Sidebar​

<script>
  import { page } from '$app/stores';

  const navigation = [
    {
      id: 'overview',
      items: [
        { id: 'dashboard', name: 'Dashboard', href: '/dashboard', icon: 'home' },
        { id: 'analytics', name: 'Analytics', href: '/analytics', icon: 'chart' },
      ],
    },
    {
      id: 'content',
      title: 'Content',
      items: [
        { id: 'posts', name: 'Posts', href: '/posts', icon: 'file' },
        { id: 'media', name: 'Media', href: '/media', icon: 'image' },
        { id: 'pages', name: 'Pages', href: '/pages', icon: 'document' },
      ],
    },
    {
      id: 'settings',
      title: 'Settings',
      collapsible: true,
      expanded: false,
      items: [
        { id: 'general', name: 'General', href: '/settings' },
        { id: 'team', name: 'Team', href: '/settings/team' },
        { id: 'billing', name: 'Billing', href: '/settings/billing' },
      ],
    },
  ];
</script>

<Sidebar
  {navigation}
  variant="dark"
  searchable
  quickLinks={[{ id: 'help', name: 'Help Center', href: '/help', icon: 'help' }]}
/>

Features​

  • Responsive: Adapts to mobile/desktop
  • Collapsible: Icons-only mode with flyouts
  • Searchable: Filter navigation items
  • Role-Based: Filter by user permissions
  • Nested: Multi-level navigation
  • Swipe-to-Dismiss: Mobile gesture support

Accessibility​

  • Keyboard navigation with arrow keys
  • Focus management
  • ARIA landmarks
  • Screen reader announcements
On this page