Responsive sidebar navigation with collapsible sections
Tags: component layout navigation
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;
}Navigation Structure
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)} />Custom Logo
<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>Quick Links
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} />Footer Content
<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>Navigation Callback
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
Related Components
- Header - Application header
- DashboardLayout - Complete dashboard
- AppShell - Base layout