Sidebar
A vertical navigation panel typically displayed on the side of the page
Basic Example
Icon collapsible sidebar with navigation menu items.
1
2<script lang="ts">
3 import { Card, Sidebar, SidebarPrimitives } from "@kareyes/aether";
4 import {
5 Home,
6 Inbox,
7 Calendar,
8 Search,
9 Settings,
10 } from "@kareyes/aether/icons";
11
12 const {
13 SidebarProvider,
14 SidebarContent,
15 SidebarGroup,
16 SidebarGroupContent,
17 SidebarGroupLabel,
18 SidebarInset,
19 SidebarMenu,
20 SidebarMenuBadge,
21 SidebarMenuButton,
22 SidebarMenuItem,
23 SidebarRail,
24 SidebarTrigger,
25 } = SidebarPrimitives;
26
27 let currentPage = $state("home");
28</script>
29
30<div class="h-[600px] overflow-hidden rounded-lg border">
31 <SidebarProvider>
32 <Sidebar collapsible="icon">
33 <SidebarContent>
34 <SidebarGroup>
35 <SidebarGroupLabel>Platform</SidebarGroupLabel>
36 <SidebarGroupContent>
37 <SidebarMenu>
38 <SidebarMenuItem>
39 <SidebarMenuButton
40 isActive={currentPage === "home"}
41 tooltipContent="Home"
42 onclick={() => (currentPage = "home")}
43 >
44 <Home class="size-4" />
45 <span>Home</span>
46 </SidebarMenuButton>
47 </SidebarMenuItem>
48 <SidebarMenuItem>
49 <SidebarMenuButton
50 isActive={currentPage === "inbox"}
51 tooltipContent="Inbox"
52 onclick={() => (currentPage = "inbox")}
53 >
54 <Inbox class="size-4" />
55 <span>Inbox</span>
56 </SidebarMenuButton>
57 <SidebarMenuBadge>24</SidebarMenuBadge>
58 </SidebarMenuItem>
59 <SidebarMenuItem>
60 <SidebarMenuButton
61 isActive={currentPage === "calendar"}
62 tooltipContent="Calendar"
63 onclick={() => (currentPage = "calendar")}
64 >
65 <Calendar class="size-4" />
66 <span>Calendar</span>
67 </SidebarMenuButton>
68 </SidebarMenuItem>
69 <SidebarMenuItem>
70 <SidebarMenuButton
71 isActive={currentPage === "search"}
72 tooltipContent="Search"
73 onclick={() => (currentPage = "search")}
74 >
75 <Search class="size-4" />
76 <span>Search</span>
77 </SidebarMenuButton>
78 </SidebarMenuItem>
79 <SidebarMenuItem>
80 <SidebarMenuButton
81 isActive={currentPage === "settings"}
82 tooltipContent="Settings"
83 onclick={() => (currentPage = "settings")}
84 >
85 <Settings class="size-4" />
86 <span>Settings</span>
87 </SidebarMenuButton>
88 </SidebarMenuItem>
89 </SidebarMenu>
90 </SidebarGroupContent>
91 </SidebarGroup>
92 </SidebarContent>
93 <SidebarRail />
94 </Sidebar>
95 <SidebarInset>
96 <header class="flex h-16 shrink-0 items-center gap-2 border-b px-4">
97 <SidebarTrigger />
98 <div class="flex flex-1 items-center justify-between">
99 <h1 class="text-lg font-semibold capitalize">
100 {currentPage}
101 </h1>
102 <span class="text-sm text-muted-foreground">
103 Click the menu icon to toggle
104 </span>
105 </div>
106 </header>
107 <div class="flex flex-1 flex-col gap-4 p-4">
108 <Card class="flex min-h-[400px] flex-1 items-center justify-center rounded-xl">
109 <p class="text-muted-foreground">
110 Main content area - {currentPage}
111 </p>
112 </Card>
113 </div>
114 </SidebarInset>
115 </SidebarProvider>
116</div>With Header and Footer
Sidebar with organization branding in header and user info in footer.
1
2<script lang="ts">
3 import { Card, Sidebar, SidebarPrimitives } from "@kareyes/aether";
4 import {
5 Home, TrendingUp, Users, FileText, Settings,
6 User, MoreHorizontal, LogOut,
7 } from "@kareyes/aether/icons";
8
9 const {
10 SidebarProvider, SidebarContent, SidebarFooter,
11 SidebarGroup, SidebarGroupContent, SidebarHeader,
12 SidebarInset, SidebarMenu, SidebarMenuAction,
13 SidebarMenuButton, SidebarMenuItem, SidebarTrigger,
14 } = SidebarPrimitives;
15</script>
16
17<SidebarProvider>
18 <Sidebar>
19 <SidebarHeader>
20 <SidebarMenu>
21 <SidebarMenuItem>
22 <SidebarMenuButton size="lg">
23 <div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-primary text-primary-foreground">
24 <span class="text-lg font-bold">A</span>
25 </div>
26 <div class="flex flex-col gap-0.5 text-start">
27 <span class="font-semibold">Acme Inc</span>
28 <span class="text-xs text-muted-foreground">Enterprise</span>
29 </div>
30 </SidebarMenuButton>
31 </SidebarMenuItem>
32 </SidebarMenu>
33 </SidebarHeader>
34 <SidebarContent>
35 <SidebarGroup>
36 <SidebarGroupContent>
37 <SidebarMenu>
38 <SidebarMenuItem>
39 <SidebarMenuButton><Home class="size-4" /><span>Dashboard</span></SidebarMenuButton>
40 </SidebarMenuItem>
41 <!-- ...more items -->
42 </SidebarMenu>
43 </SidebarGroupContent>
44 </SidebarGroup>
45 </SidebarContent>
46 <SidebarFooter>
47 <SidebarMenu>
48 <SidebarMenuItem>
49 <SidebarMenuButton>
50 <User class="size-4" />
51 <div class="flex flex-col gap-0.5 text-start">
52 <span class="text-sm font-medium">John Doe</span>
53 <span class="text-xs text-muted-foreground">john@acme.com</span>
54 </div>
55 </SidebarMenuButton>
56 <SidebarMenuAction><LogOut class="size-4" /></SidebarMenuAction>
57 </SidebarMenuItem>
58 </SidebarMenu>
59 </SidebarFooter>
60 </Sidebar>
61 <SidebarInset>
62 <!-- Main content -->
63 </SidebarInset>
64</SidebarProvider>With Nested Submenus
Organize navigation with collapsible submenus for better hierarchy.
1
2<script lang="ts">
3 import { Sidebar, SidebarPrimitives } from "@kareyes/aether";
4 import { Folder, ChevronRight } from "@kareyes/aether/icons";
5
6 const {
7 SidebarProvider, SidebarContent, SidebarGroup,
8 SidebarGroupContent, SidebarGroupLabel, SidebarInset,
9 SidebarMenu, SidebarMenuButton, SidebarMenuItem,
10 SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem,
11 SidebarTrigger,
12 } = SidebarPrimitives;
13</script>
14
15<SidebarProvider>
16 <Sidebar>
17 <SidebarContent>
18 <SidebarGroup>
19 <SidebarGroupLabel>Projects</SidebarGroupLabel>
20 <SidebarGroupContent>
21 <SidebarMenu>
22 <SidebarMenuItem>
23 <SidebarMenuButton>
24 <Folder class="size-4" />
25 <span>Design System</span>
26 <ChevronRight class="ml-auto size-4 transition-transform group-data-[state=open]:rotate-90" />
27 </SidebarMenuButton>
28 <SidebarMenuSub>
29 <SidebarMenuSubItem>
30 <SidebarMenuSubButton><span>Components</span></SidebarMenuSubButton>
31 </SidebarMenuSubItem>
32 <SidebarMenuSubItem>
33 <SidebarMenuSubButton><span>Typography</span></SidebarMenuSubButton>
34 </SidebarMenuSubItem>
35 </SidebarMenuSub>
36 </SidebarMenuItem>
37 </SidebarMenu>
38 </SidebarGroupContent>
39 </SidebarGroup>
40 </SidebarContent>
41 </Sidebar>
42 <SidebarInset>
43 <!-- Main content -->
44 </SidebarInset>
45</SidebarProvider>With Actions and Badges
Menu items with notification counts and quick actions.
1
2<script lang="ts">
3 import { Sidebar, SidebarPrimitives } from "@kareyes/aether";
4 import { Mail, Bell, MessageSquare, Star, Plus, Settings, MoreHorizontal } from "@kareyes/aether/icons";
5
6 const {
7 SidebarProvider, SidebarContent, SidebarGroup,
8 SidebarGroupContent, SidebarGroupLabel, SidebarInset,
9 SidebarMenu, SidebarMenuAction, SidebarMenuBadge,
10 SidebarMenuButton, SidebarMenuItem, SidebarSeparator,
11 SidebarTrigger,
12 } = SidebarPrimitives;
13</script>
14
15<SidebarProvider>
16 <Sidebar>
17 <SidebarContent>
18 <SidebarGroup>
19 <SidebarGroupLabel>Communications</SidebarGroupLabel>
20 <SidebarGroupContent>
21 <SidebarMenu>
22 <SidebarMenuItem>
23 <SidebarMenuButton><Mail class="size-4" /><span>Messages</span></SidebarMenuButton>
24 <SidebarMenuBadge>12</SidebarMenuBadge>
25 <SidebarMenuAction title="Compose"><Plus class="size-4" /></SidebarMenuAction>
26 </SidebarMenuItem>
27 <SidebarMenuItem>
28 <SidebarMenuButton><Bell class="size-4" /><span>Notifications</span></SidebarMenuButton>
29 <SidebarMenuBadge>99+</SidebarMenuBadge>
30 <SidebarMenuAction title="Settings"><Settings class="size-4" /></SidebarMenuAction>
31 </SidebarMenuItem>
32 </SidebarMenu>
33 </SidebarGroupContent>
34 </SidebarGroup>
35 <SidebarSeparator />
36 <SidebarGroup>
37 <SidebarGroupLabel>Favorites</SidebarGroupLabel>
38 <SidebarGroupContent>
39 <SidebarMenu>
40 <SidebarMenuItem>
41 <SidebarMenuButton><Star class="size-4 fill-yellow-400 text-yellow-400" /><span>Starred Items</span></SidebarMenuButton>
42 <SidebarMenuBadge>5</SidebarMenuBadge>
43 </SidebarMenuItem>
44 </SidebarMenu>
45 </SidebarGroupContent>
46 </SidebarGroup>
47 </SidebarContent>
48 </Sidebar>
49 <SidebarInset>
50 <!-- Main content -->
51 </SidebarInset>
52</SidebarProvider>With Search and Multiple Groups
Complex sidebar with search input and organized sections.
1
2<script lang="ts">
3 import { Sidebar, SidebarPrimitives } from "@kareyes/aether";
4 import { Home, TrendingUp, FileText, Folder, Plus, BookOpen, HelpCircle } from "@kareyes/aether/icons";
5
6 const {
7 SidebarProvider, SidebarContent, SidebarGroup,
8 SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel,
9 SidebarInput, SidebarInset, SidebarMenu, SidebarMenuButton,
10 SidebarMenuItem, SidebarSeparator, SidebarTrigger,
11 } = SidebarPrimitives;
12</script>
13
14<SidebarProvider>
15 <Sidebar>
16 <SidebarContent>
17 <SidebarGroup>
18 <SidebarGroupContent>
19 <SidebarInput type="search" placeholder="Search..." />
20 </SidebarGroupContent>
21 </SidebarGroup>
22 <SidebarSeparator />
23 <SidebarGroup>
24 <SidebarGroupLabel>Platform</SidebarGroupLabel>
25 <SidebarGroupContent>
26 <SidebarMenu>
27 <SidebarMenuItem>
28 <SidebarMenuButton><Home class="size-4" /><span>Dashboard</span></SidebarMenuButton>
29 </SidebarMenuItem>
30 <!-- ...more items -->
31 </SidebarMenu>
32 </SidebarGroupContent>
33 </SidebarGroup>
34 <SidebarSeparator />
35 <SidebarGroup>
36 <SidebarGroupLabel>Projects</SidebarGroupLabel>
37 <SidebarGroupAction title="Add Project"><Plus class="size-4" /></SidebarGroupAction>
38 <SidebarGroupContent>
39 <SidebarMenu>
40 {#each projects as project}
41 <SidebarMenuItem>
42 <SidebarMenuButton><Folder class="size-4" /><span>{project.name}</span></SidebarMenuButton>
43 </SidebarMenuItem>
44 {/each}
45 </SidebarMenu>
46 </SidebarGroupContent>
47 </SidebarGroup>
48 </SidebarContent>
49 </Sidebar>
50 <SidebarInset>
51 <!-- Main content -->
52 </SidebarInset>
53</SidebarProvider>Floating Variant
Sidebar floats over content with shadow effect.
1
2<script lang="ts">
3 import { Sidebar, SidebarPrimitives } from "@kareyes/aether";
4 import { Home, Code, Database, Cloud, Shield } from "@kareyes/aether/icons";
5
6 const {
7 SidebarProvider, SidebarContent, SidebarGroup,
8 SidebarGroupContent, SidebarGroupLabel, SidebarInset,
9 SidebarMenu, SidebarMenuButton, SidebarMenuItem,
10 SidebarRail, SidebarTrigger,
11 } = SidebarPrimitives;
12</script>
13
14<SidebarProvider>
15 <Sidebar variant="floating" collapsible="icon">
16 <SidebarContent>
17 <SidebarGroup>
18 <SidebarGroupLabel>Menu</SidebarGroupLabel>
19 <SidebarGroupContent>
20 <SidebarMenu>
21 <SidebarMenuItem>
22 <SidebarMenuButton tooltipContent="Dashboard">
23 <Home class="size-4" /><span>Dashboard</span>
24 </SidebarMenuButton>
25 </SidebarMenuItem>
26 <!-- ...more items -->
27 </SidebarMenu>
28 </SidebarGroupContent>
29 </SidebarGroup>
30 </SidebarContent>
31 <SidebarRail />
32 </Sidebar>
33 <SidebarInset>
34 <!-- Main content -->
35 </SidebarInset>
36</SidebarProvider>Loading State
Skeleton placeholders while content is loading.
1
2<script lang="ts">
3 import { Sidebar, SidebarPrimitives } from "@kareyes/aether";
4
5 const {
6 SidebarProvider, SidebarContent, SidebarGroup,
7 SidebarGroupContent, SidebarGroupLabel, SidebarInset,
8 SidebarMenu, SidebarMenuSkeleton, SidebarTrigger,
9 } = SidebarPrimitives;
10</script>
11
12<SidebarProvider>
13 <Sidebar>
14 <SidebarContent>
15 <SidebarGroup>
16 <SidebarGroupLabel>Loading...</SidebarGroupLabel>
17 <SidebarGroupContent>
18 <SidebarMenu>
19 <SidebarMenuSkeleton />
20 <SidebarMenuSkeleton />
21 <SidebarMenuSkeleton />
22 <SidebarMenuSkeleton />
23 </SidebarMenu>
24 </SidebarGroupContent>
25 </SidebarGroup>
26 </SidebarContent>
27 </Sidebar>
28 <SidebarInset>
29 <!-- Main content -->
30 </SidebarInset>
31</SidebarProvider>Button Size Variants
Small, default, and large button sizes for different use cases.
1
2<script lang="ts">
3 import { Sidebar, SidebarPrimitives } from "@kareyes/aether";
4 import { Home, Settings, Inbox, Calendar, User, Zap } from "@kareyes/aether/icons";
5
6 const {
7 SidebarProvider, SidebarContent, SidebarGroup,
8 SidebarGroupContent, SidebarGroupLabel, SidebarInset,
9 SidebarMenu, SidebarMenuButton, SidebarMenuItem,
10 SidebarTrigger,
11 } = SidebarPrimitives;
12</script>
13
14<SidebarProvider>
15 <Sidebar>
16 <SidebarContent>
17 <SidebarGroup>
18 <SidebarGroupLabel>Small Buttons</SidebarGroupLabel>
19 <SidebarGroupContent>
20 <SidebarMenu>
21 <SidebarMenuItem>
22 <SidebarMenuButton size="sm"><Home class="size-4" /><span>Home</span></SidebarMenuButton>
23 </SidebarMenuItem>
24 </SidebarMenu>
25 </SidebarGroupContent>
26 </SidebarGroup>
27 <SidebarGroup>
28 <SidebarGroupLabel>Default Buttons</SidebarGroupLabel>
29 <SidebarGroupContent>
30 <SidebarMenu>
31 <SidebarMenuItem>
32 <SidebarMenuButton><Inbox class="size-4" /><span>Inbox</span></SidebarMenuButton>
33 </SidebarMenuItem>
34 </SidebarMenu>
35 </SidebarGroupContent>
36 </SidebarGroup>
37 <SidebarGroup>
38 <SidebarGroupLabel>Large Buttons</SidebarGroupLabel>
39 <SidebarGroupContent>
40 <SidebarMenu>
41 <SidebarMenuItem>
42 <SidebarMenuButton size="lg"><User class="size-4" /><span>Profile</span></SidebarMenuButton>
43 </SidebarMenuItem>
44 </SidebarMenu>
45 </SidebarGroupContent>
46 </SidebarGroup>
47 </SidebarContent>
48 </Sidebar>
49 <SidebarInset>
50 <!-- Main content -->
51 </SidebarInset>
52</SidebarProvider>Link-Based Navigation
Using the child snippet pattern with anchor tags for proper navigation links.
1
2<script lang="ts">
3 import { Sidebar, SidebarPrimitives } from "@kareyes/aether";
4 import { BookOpen, Code, FileText, Database, HelpCircle } from "@kareyes/aether/icons";
5
6 const {
7 SidebarProvider, SidebarContent, SidebarGroup,
8 SidebarGroupContent, SidebarGroupLabel, SidebarInset,
9 SidebarMenu, SidebarMenuButton, SidebarMenuItem,
10 SidebarTrigger,
11 } = SidebarPrimitives;
12</script>
13
14<SidebarProvider>
15 <Sidebar>
16 <SidebarContent>
17 <SidebarGroup>
18 <SidebarGroupLabel>Documentation</SidebarGroupLabel>
19 <SidebarGroupContent>
20 <SidebarMenu>
21 <SidebarMenuItem>
22 <SidebarMenuButton isActive>
23 {#snippet child({ props })}
24 <a href="/getting-started" {...props}>
25 <BookOpen class="size-4" />
26 <span>Getting Started</span>
27 </a>
28 {/snippet}
29 </SidebarMenuButton>
30 </SidebarMenuItem>
31 <SidebarMenuItem>
32 <SidebarMenuButton>
33 {#snippet child({ props })}
34 <a href="/components" {...props}>
35 <Code class="size-4" />
36 <span>Components</span>
37 </a>
38 {/snippet}
39 </SidebarMenuButton>
40 </SidebarMenuItem>
41 </SidebarMenu>
42 </SidebarGroupContent>
43 </SidebarGroup>
44 </SidebarContent>
45 </Sidebar>
46 <SidebarInset>
47 <!-- Main content -->
48 </SidebarInset>
49</SidebarProvider>Add the following colors to your CSS file
We'll go over the colors later in the theming section.
src/routes/layout.css
:root {
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.439 0 0);
}
Structure
A Sidebar component is composed of the following parts:
Sidebar.Provider- Handles collapsible state.Sidebar.Root- The sidebar container.Sidebar.HeaderandSidebar.Footer- Sticky at the top and bottom of the sidebar.Sidebar.Content- Scrollable content.Sidebar.Group- Section within theSidebar.Content.Sidebar.Trigger- Trigger for theSidebar.

Usage
src/routes/+layout.svelte
<script lang="ts">
import { SidebarPrimitives as Sidebar } from "@kareyes/aether";
import AppSidebar from "$lib/components/app-sidebar.svelte";
let { children } = $props();
</script>
<Sidebar.Provider>
<AppSidebar />
<main>
<Sidebar.Trigger />
{@render children?.()}
</main>
</Sidebar.Provider>
src/lib/components/app-sidebar.svelte
<script lang="ts">
import { SidebarPrimitives as Sidebar } from "@kareyes/aether";
</script>
<Sidebar.Root>
<Sidebar.Header />
<Sidebar.Content>
<Sidebar.Group />
<Sidebar.Group />
</Sidebar.Content>
<Sidebar.Footer />
</Sidebar.Root>
Your First Sidebar
Let's start with the most basic sidebar. A collapsible sidebar with a menu.
Add a Sidebar.Provider and Sidebar.Trigger at the root of your application.
src/routes/+layout.svelte
<script lang="ts">
import { SidebarPrimitives as Sidebar } from "@kareyes/aether";
import AppSidebar from "$lib/components/app-sidebar.svelte";
let { children } = $props();
</script>
<Sidebar.Provider>
<AppSidebar />
<main>
<Sidebar.Trigger />
{@render children?.()}
</main>
</Sidebar.Provider>
Create a new sidebar component at src/lib/components/app-sidebar.svelte.
src/lib/components/app-sidebar.svelte
<script lang="ts">
import { SidebarPrimitives as Sidebar } from "@kareyes/aether";
</script>
<Sidebar.Root>
<Sidebar.Content />
</Sidebar.Root>
Now, let's add a Sidebar.Menu to the sidebar.
We'll use the Sidebar.Menu component in a Sidebar.Group.
src/lib/components/app-sidebar.svelte
<script lang="ts">
import CalendarIcon from "@lucide/svelte/icons/calendar";
import HouseIcon from "@lucide/svelte/icons/house";
import InboxIcon from "@lucide/svelte/icons/inbox";
import SearchIcon from "@lucide/svelte/icons/search";
import SettingsIcon from "@lucide/svelte/icons/settings";
import { SidebarPrimitives as Sidebar } from "@kareyes/aether";
// Menu items.
const items = [
{
title: "Home",
url: "#",
icon: HouseIcon,
},
{
title: "Inbox",
url: "#",
icon: InboxIcon,
},
{
title: "Calendar",
url: "#",
icon: CalendarIcon,
},
{
title: "Search",
url: "#",
icon: SearchIcon,
},
{
title: "Settings",
url: "#",
icon: SettingsIcon,
},
];
</script>
<Sidebar.Root>
<Sidebar.Content>
<Sidebar.Group>
<Sidebar.GroupLabel>Application</Sidebar.GroupLabel>
<Sidebar.GroupContent>
<Sidebar.Menu>
{#each items as item (item.title)}
<Sidebar.MenuItem>
<Sidebar.MenuButton>
{#snippet child({ props })}
<a href={item.url} {...props}>
<item.icon />
<span>{item.title}</span>
</a>
{/snippet}
</Sidebar.MenuButton>
</Sidebar.MenuItem>
{/each}
</Sidebar.Menu>
</Sidebar.GroupContent>
</Sidebar.Group>
</Sidebar.Content>
</Sidebar.Root>
You've created your first sidebar.
Your first sidebar.
Components
The components in the sidebar-*.svelte files are built to be composable i.e you build your sidebar by putting the provided components together. They also compose well with other shadcn-svelte components such as DropdownMenu, Collapsible, Dialog, etc.
If you need to change the code in the sidebar-*.svelte files, you are encouraged to do so. The code is yours. Use the provided components as a starting point to build your own
In the next sections, we'll go over each component and how to use them.
Sidebar.Provider
The Sidebar.Provider component is used to provide the sidebar context to the Sidebar component. You should always wrap your application in a Sidebar.Provider component.
Props
| Name | Type | Description |
|---|---|---|
open |
boolean |
Open state of the sidebar (bindable). |
onOpenChange |
(open: boolean) => void |
A callback fired after the open state of the sidebar changes if uncontrolled, and before the sidebar opens or closes if controlled. |
Width
If you have a single sidebar in your application, you can use the SIDEBAR_WIDTH and SIDEBAR_WIDTH_MOBILE constants in src/lib/components/ui/sidebar/constants.ts to set the width of the sidebar.
src/lib/components/ui/sidebar/constants.ts
export const SIDEBAR_WIDTH = "16rem";
export const SIDEBAR_WIDTH_MOBILE = "18rem";
For multiple sidebars in your application, you can use the style prop to set the width of the sidebar.
To set the width of the sidebar, you can use the --sidebar-width and --sidebar-width-mobile CSS variables in the style prop.
<Sidebar.Provider
style="--sidebar-width: 20rem; --sidebar-width-mobile: 20rem;"
>
<Sidebar.Root />
</Sidebar.Provider>
This will not only handle the width of the sidebar but also the layout spacing.
Keyboard Shortcut
The SIDEBAR_KEYBOARD_SHORTCUT variable in src/lib/components/ui/sidebar/constants.ts is used to set the keyboard shortcut used to open and close the sidebar.
To trigger the sidebar, you use the cmd+b keyboard shortcut on Mac and ctrl+b on Windows.
You can change the keyboard shortcut by changing the value of the SIDEBAR_KEYBOARD_SHORTCUT variable.
src/lib/components/ui/sidebar/constants.ts
export const SIDEBAR_KEYBOARD_SHORTCUT = "b";
Sidebar.Root
The main Sidebar component used to render a collapsible sidebar.
<script lang="ts">
import { SidebarPrimitives as Sidebar } from "@kareyes/aether";
</script>
<Sidebar.Root />
Props
| Property | Type | Description |
|---|---|---|
side |
left or right |
The side of the sidebar. |
variant |
sidebar, floating, or inset |
The variant of the sidebar. |
collapsible |
offcanvas, icon, or none |
Collapsible state of the sidebar. |
side
Use the side prop to change the side of the sidebar.
Available options are left and right.
<Sidebar.Root side="left | right" />
variant
Use the variant prop to change the variant of the sidebar.
Available options are sidebar, floating and inset.
<Sidebar.Root variant="sidebar | floating | inset" />
Note: If you use the inset variant, remember to wrap your main content in a SidebarInset component.
<Sidebar.Provider>
<Sidebar.Root variant="inset">
<Sidebar.Inset>
<main>
</main>
</Sidebar.Inset>
</Sidebar.Root>
</Sidebar.Provider>
collapsible
Use the collapsible prop to make the sidebar collapsible.
Available options are offcanvas, icon and none.
<Sidebar.Root collapsible="offcanvas | icon | none" />
| Prop | Description |
|---|---|
offcanvas |
A collapsible sidebar that slides in from the left or right. |
icon |
A sidebar that collapses to icons. |
none |
A non-collapsible sidebar. |
useSidebar
The useSidebar function is used to hook into the sidebar context. It returns a reactive class instance, so it cannot be destructured. Additionally, it must be called during the lifecycle of the component.
<script lang="ts">
import { useSidebar } from "@kareyes/aether";
sidebar.state;
sidebar.isMobile;
sidebar.toggle();
</script>
| Property | Type | Description |
|---|---|---|
state |
expanded or collapsed |
The current state of the sidebar. |
open |
boolean |
Whether the sidebar is open. |
setOpen |
(open: boolean) => void |
Sets the open state of the sidebar. |
openMobile |
boolean |
Whether the sidebar is open on mobile. |
setOpenMobile |
(open: boolean) => void |
Sets the open state of the sidebar on mobile. |
isMobile |
boolean |
Whether the sidebar is on mobile. |
toggle |
() => void |
Toggles the sidebar. Desktop and mobile. |
Sidebar.Header
Use the Sidebar.Header component to add a sticky header to the sidebar.
The following example adds a <DropdownMenu> to the Sidebar.Header.
A sidebar header with a dropdown menu.
src/lib/components/app-sidebar.svelte
<Sidebar.Root>
<Sidebar.Header>
<Sidebar.Menu>
<Sidebar.MenuItem>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Sidebar.MenuButton {...props}>
Select Workspace
<ChevronDown class="ms-auto" />
</Sidebar.MenuButton>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content class="w-(--bits-dropdown-menu-anchor-width)">
<DropdownMenu.Item>
<span>Acme Inc</span>
</DropdownMenu.Item>
<DropdownMenu.Item>
<span>Acme Corp.</span>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</Sidebar.MenuItem>
</Sidebar.Menu>
</Sidebar.Header>
</Sidebar.Root>
Sidebar.Footer
Use the Sidebar.Footer component to add a sticky footer to the sidebar.
The following example adds a <DropdownMenu> to the Sidebar.Footer.
A sidebar footer with a dropdown menu.
src/lib/components/app-sidebar.svelte
<Sidebar.Provider>
<Sidebar.Root>
<Sidebar.Header />
<Sidebar.Content />
<Sidebar.Footer>
<Sidebar.Menu>
<Sidebar.MenuItem>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Sidebar.MenuButton
{...props}
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
Username
<ChevronUp class="ms-auto" />
</Sidebar.MenuButton>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content
side="top"
class="w-(--bits-dropdown-menu-anchor-width)"
>
<DropdownMenu.Item>
<span>Account</span>
</DropdownMenu.Item>
<DropdownMenu.Item>
<span>Billing</span>
</DropdownMenu.Item>
<DropdownMenu.Item>
<span>Sign out</span>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</Sidebar.MenuItem>
</Sidebar.Menu>
</Sidebar.Footer>
</Sidebar.Root>
<Sidebar.Inset>
<header class="flex h-12 items-center justify-between px-4">
<Sidebar.Trigger />
</header>
</Sidebar.Inset>
</Sidebar.Provider>
Sidebar.Content
The Sidebar.Content component is used to wrap the content of the sidebar. This is where you add your Sidebar.Group components. It is scrollable.
<Sidebar.Root>
<Sidebar.Content>
<Sidebar.Group />
<Sidebar.Group />
</Sidebar.Content>
</Sidebar.Root>
Sidebar.Group
Use the Sidebar.Group component to create a section within the sidebar.
A Sidebar.Group has a Sidebar.GroupLabel, a Sidebar.GroupContent and an optional Sidebar.GroupAction.
A sidebar group.
<Sidebar.Root>
<Sidebar.Content>
<Sidebar.Group>
<Sidebar.GroupLabel>Application</Sidebar.GroupLabel>
<Sidebar.GroupAction>
<Plus /> <span class="sr-only">Add Project</span>
</Sidebar.GroupAction>
<Sidebar.GroupContent></Sidebar.GroupContent>
</Sidebar.Group>
</Sidebar.Content>
</Sidebar.Root>
Collapsible Sidebar.Group
To make a Sidebar.Group collapsible, wrap it in a Collapsible.
A collapsible sidebar group.
<Collapsible.Root open class="group/collapsible">
<Sidebar.Group>
<Sidebar.GroupLabel>
{#snippet child({ props })}
<Collapsible.Trigger {...props}>
Help
<ChevronDown
class="ms-auto transition-transform group-data-[state=open]/collapsible:rotate-180"
/>
</Collapsible.Trigger>
{/snippet}
</Sidebar.GroupLabel>
<Collapsible.Content>
<Sidebar.GroupContent />
</Collapsible.Content>
</Sidebar.Group>
</Collapsible.Root>
Note: We wrap the Collapsible.Trigger in a Sidebar.GroupLabel to render a button.
Sidebar.GroupAction
Use the Sidebar.GroupAction component to add an action to a Sidebar.Group.
<Sidebar.Group>
<Sidebar.GroupLabel>Projects</Sidebar.GroupLabel>
<Sidebar.GroupAction title="Add Project">
<Plus /> <span class="sr-only">Add Project</span>
</Sidebar.GroupAction>
<Sidebar.GroupContent />
</Sidebar.Group>
A sidebar group with an action button.
Sidebar.Menu
The Sidebar.Menu component is used for building a menu within a Sidebar.Group.
A Sidebar.Menu is composed of Sidebar.MenuItem, Sidebar.MenuButton, Sidebar.MenuAction, and Sidebar.MenuSub components.

Here's an example of a Sidebar.Menu component rendering a list of projects.
A sidebar menu with a list of projects.
<Sidebar.Root>
<Sidebar.Content>
<Sidebar.Group>
<Sidebar.GroupLabel>Projects</Sidebar.GroupLabel>
<Sidebar.GroupContent>
<Sidebar.Menu>
{#each projects as project}
<Sidebar.MenuItem>
<Sidebar.MenuButton>
{#snippet child({ props })}
<a href={project.url} {...props}>
<project.icon />
<span>{project.name}</span>
</a>
{/snippet}
</Sidebar.MenuButton>
</Sidebar.MenuItem>
{/each}
</Sidebar.Menu>
</Sidebar.GroupContent>
</Sidebar.Group>
</Sidebar.Content>
</Sidebar.Root>
Sidebar.MenuButton
The Sidebar.MenuButton component is used to render a menu button within a Sidebar.Menu.
Link or Anchor
By default, the Sidebar.MenuButton renders a button, but you can use the child snippet to render a different component such as an <a> tag.
<Sidebar.MenuButton>
{#snippet child({ props })}
<a href="/home" {...props}> Home </a>
{/snippet}
</Sidebar.MenuButton>
Icon and Label
You can render an icon and a truncated label inside the button. Remember to wrap the label in a <span> tag.
<Sidebar.MenuButton>
{#snippet child({ props })}
<a href="/home" {...props}>
<House />
<span>Home</span>
</a>
{/snippet}
</Sidebar.MenuButton>
isActive
Use the isActive prop to mark a menu item as active.
<Sidebar.MenuButton isActive>
{#snippet child({ props })}
<a href="/home" {...props}>
<House />
<span>Home</span>
</a>
{/snippet}
</Sidebar.MenuButton>
Sidebar.MenuAction
The Sidebar.MenuAction component is used to render a menu action within a Sidebar.Menu.
This button works independently of the Sidebar.MenuButton, i.e. you can have the Sidebar.MenuButton as a clickable link and the Sidebar.MenuAction as a button.
<Sidebar.MenuItem>
<Sidebar.MenuButton>
{#snippet child({ props })}
<a href="/home" {...props}>
<House />
<span>Home</span>
</a>
{/snippet}
</Sidebar.MenuButton>
<Sidebar.MenuAction>
<Plus /> <span class="sr-only">Add Project</span>
</Sidebar.MenuAction>
</Sidebar.MenuItem>
DropdownMenu
Here's an example of a Sidebar.MenuAction that renders a DropdownMenu.
A sidebar menu action with a dropdown menu.
<Sidebar.MenuItem>
<Sidebar.MenuButton>
{#snippet child({ props })}
<a href="##" {...props}>
<House />
<span>Home</span>
</a>
{/snippet}
</Sidebar.MenuButton>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Sidebar.MenuAction {...props}>
<Ellipsis />
</Sidebar.MenuAction>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content side="right" align="start">
<DropdownMenu.Item>
<span>Edit Project</span>
</DropdownMenu.Item>
<DropdownMenu.Item>
<span>Delete Project</span>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</Sidebar.MenuItem>
Sidebar.MenuSub
The Sidebar.MenuSub component is used to render a submenu within a Sidebar.Menu.
Use Sidebar.MenuSubItem and Sidebar.MenuSubButton to render a submenu item.
A sidebar menu sub.
<Sidebar.MenuItem>
<Sidebar.MenuButton />
<Sidebar.MenuSub>
<Sidebar.MenuSubItem>
<Sidebar.MenuSubButton />
</Sidebar.MenuSubItem>
<Sidebar.MenuSubItem>
<Sidebar.MenuSubButton />
</Sidebar.MenuSubItem>
</Sidebar.MenuSub>
</Sidebar.MenuItem>
Collapsible Sidebar.Menu
To make a Sidebar.Menu collapsible, wrap it and the Sidebar.MenuSub components in a Collapsible.
A collapsible sidebar menu.
<Sidebar.Menu>
<Collapsible.Root open class="group/collapsible">
<Sidebar.MenuItem>
<Collapsible.Trigger>
{#snippet child({ props })}
<Sidebar.MenuButton {...props} />
{/snippet}
</Collapsible.Trigger>
<Collapsible.Content>
<Sidebar.MenuSub>
<Sidebar.MenuSubItem />
</Sidebar.MenuSub>
</Collapsible.Content>
</Sidebar.MenuItem>
</Collapsible.Root>
</Sidebar.Menu>
Sidebar.MenuBadge
The Sidebar.MenuBadge component is used to render a badge within a Sidebar.MenuItem.
A sidebar menu badge.
<Sidebar.MenuItem>
<Sidebar.MenuButton />
<Sidebar.MenuBadge>24</Sidebar.MenuBadge>
</Sidebar.MenuItem>
Sidebar.MenuSkeleton
The Sidebar.MenuSkeleton component is used to render a skeleton within a Sidebar.MenuItem. You can use this to show a loading state while waiting for data to load.
<Sidebar.Menu>
{#each Array.from({ length: 5 }) as _, index (index)}
<Sidebar.MenuItem>
<Sidebar.MenuSkeleton />
</Sidebar.MenuItem>
{/each}
</Sidebar.Menu>
Sidebar.Separator
The Sidebar.Separator component is used to render a separator within a Sidebar.
<Sidebar.Root>
<Sidebar.Header />
<Sidebar.Separator />
<Sidebar.Content>
<Sidebar.Group />
<Sidebar.Separator />
<Sidebar.Group />
</Sidebar.Content>
</Sidebar.Root>
Sidebar.Trigger
Use the Sidebar.Trigger component to render a button that toggles the sidebar.
The Sidebar.Trigger component must be used within a Sidebar.Provider.
<Sidebar.Provider>
<Sidebar.Root />
<main>
<Sidebar.Trigger />
</main>
</Sidebar.Provider>
Custom Trigger
To create a custom trigger, you can use the useSidebar hook.
<script lang="ts">
import { useSidebar } from "@kareyes/aether";
const sidebar = useSidebar();
</script>
<button onclick={() => sidebar.toggle()}>Toggle Sidebar</button>
Sidebar.Rail
The Sidebar.Rail component is used to render a rail within a Sidebar.Root. This rail can be used to toggle the sidebar.
<Sidebar.Root>
<Sidebar.Header />
<Sidebar.Content>
<Sidebar.Group />
</Sidebar.Content>
<Sidebar.Footer />
<Sidebar.Rail />
</Sidebar.Root>
Controlled Sidebar
Use Svelte's Function Binding to control the sidebar state.
A controlled sidebar.
<script lang="ts">
import { SidebarPrimitives as Sidebar } from "@kareyes/aether";
let myOpen = $state(true);
</script>
<Sidebar.Provider bind:open={() => myOpen, (newOpen) => (myOpen = newOpen)}>
<Sidebar.Root />
</Sidebar.Provider>
<Sidebar.Provider bind:open>
<Sidebar.Root />
</Sidebar.Provider>
Theming
We use the following CSS variables to theme the sidebar.
:root {
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.439 0 0);
}
We intentionally use different variables for the sidebar and the rest of the application to make it easy to have a sidebar that is styled differently from the rest of the application. Think a sidebar with a darker shade from the main application.
Styling
Here are some tips for styling the sidebar based on different states.
- Styling an element based on the sidebar collapsible state. The following will hide the
Sidebar.Groupwhen the sidebar is iniconmode.
<Sidebar.Root collapsible="icon">
<Sidebar.Content>
<Sidebar.Group class="group-data-[collapsible=icon]:hidden" />
</Sidebar.Content>
</Sidebar.Root>
- Styling a menu action based on the menu button active state. The following will force the menu action to be visible when the menu button is active.
<Sidebar.MenuItem>
<Sidebar.MenuButton />
<Sidebar.MenuAction
class="peer-data-[active=true]/menu-button:opacity-100"
/>
</Sidebar.MenuItem>
You can find more tips on using states for styling in this Twitter thread.
Sidebar
Main sidebar container.
| Prop | Type | Default | Description |
|---|---|---|---|
side |
"left" | "right" |
"left" |
Position of sidebar |
variant |
"sidebar" | "floating" | "inset" |
"sidebar" |
Visual variant |
collapsible |
"offcanvas" | "icon" | "none" |
"offcanvas" |
Collapse behavior |
ref |
HTMLDivElement |
- | Element reference (bindable) |
class |
string |
- | Additional CSS classes |
SidebarTrigger
Toggle button for sidebar.
| Prop | Type | Default | Description |
|---|---|---|---|
ref |
HTMLButtonElement |
- | Button reference (bindable) |
class |
string |
- | Additional CSS classes |
onclick |
(e: MouseEvent) => void |
- | Click handler |
SidebarMenuButton
Interactive menu item button.
| Prop | Type | Default | Description |
|---|---|---|---|
variant |
"default" | "outline" |
"default" |
Button variant |
size |
"sm" | "default" | "lg" |
"default" |
Button size |
isActive |
boolean |
false |
Active state |
tooltipContent |
string |
- | Tooltip text (shown in icon mode) |
tooltipContentProps |
object |
- | Additional tooltip props |
ref |
HTMLButtonElement |
- | Button reference (bindable) |
class |
string |
- | Additional CSS classes |
SidebarContent
Scrollable content container.
| Prop | Type | Default | Description |
|---|---|---|---|
ref |
HTMLDivElement |
- | Element reference (bindable) |
class |
string |
- | Additional CSS classes |
SidebarHeader
Header section of sidebar.
| Prop | Type | Default | Description |
|---|---|---|---|
ref |
HTMLDivElement |
- | Element reference (bindable) |
class |
string |
- | Additional CSS classes |
SidebarFooter
Footer section of sidebar.
| Prop | Type | Default | Description |
|---|---|---|---|
ref |
HTMLDivElement |
- | Element reference (bindable) |
class |
string |
- | Additional CSS classes |
SidebarGroup
Grouping container for menu items.
| Prop | Type | Default | Description |
|---|---|---|---|
ref |
HTMLDivElement |
- | Element reference (bindable) |
class |
string |
- | Additional CSS classes |
SidebarGroupLabel
Label/title for a group.
| Prop | Type | Default | Description |
|---|---|---|---|
ref |
HTMLDivElement |
- | Element reference (bindable) |
class |
string |
- | Additional CSS classes |
SidebarGroupContent
Content container for group items.
| Prop | Type | Default | Description |
|---|---|---|---|
ref |
HTMLDivElement |
- | Element reference (bindable) |
class |
string |
- | Additional CSS classes |
SidebarGroupAction
Action button for group.
| Prop | Type | Default | Description |
|---|---|---|---|
ref |
HTMLButtonElement |
- | Button reference (bindable) |
class |
string |
- | Additional CSS classes |
SidebarMenu
Container for menu items.
| Prop | Type | Default | Description |
|---|---|---|---|
ref |
HTMLUListElement |
- | List element reference (bindable) |
class |
string |
- | Additional CSS classes |
SidebarMenuItem
Individual menu item wrapper.
| Prop | Type | Default | Description |
|---|---|---|---|
ref |
HTMLLIElement |
- | List item reference (bindable) |
class |
string |
- | Additional CSS classes |
useSidebar Hook
Access sidebar context and methods.
Returns an object with:
state:"expanded" | "collapsed"- Current stateopen:boolean- Whether sidebar is opensetOpen:(value: boolean) => void- Set open stateopenMobile:boolean- Mobile sheet open statesetOpenMobile:(value: boolean) => void- Set mobile stateisMobile:boolean- Whether in mobile viewtoggle:() => void- Toggle sidebartoggleMobile:() => void- Toggle mobile sidebar
Keyboard Shortcuts
Default keyboard shortcut: Cmd+B (Mac) / Ctrl+B (Windows/Linux)
Configure custom shortcut:
// In constants.ts
export const SIDEBAR_KEYBOARD_SHORTCUT = "b"; // Change to desired key
State Persistence
The sidebar automatically persists its state using cookies:
- Cookie name:
sidebar:state - Max age: 7 days
- Remembers collapsed/expanded state across sessions
Accessibility
The Sidebar component follows accessibility best practices:
Keyboard Navigation:
Tab- Navigate through menu itemsEnter/Space- Activate menu itemsCmd+B/Ctrl+B- Toggle sidebar
Screen Reader Support:
- Proper ARIA labels and roles
- Hidden content when collapsed
- Accessible tooltips in icon mode
Focus Management:
- Visible focus indicators
- Logical tab order
- Focus trap in mobile sheet
Responsive Behavior
Desktop (≥768px):
- Full sidebar with collapse functionality
- Hover rail for expanding from icon mode
- Keyboard shortcuts active
Mobile (<768px):
- Converts to Sheet overlay
- Triggered by SidebarTrigger
- Full-width content when hidden
Best Practices
- Use SidebarProvider - Always wrap your layout with SidebarProvider
- Organize with Groups - Use SidebarGroup to organize related items
- Active States - Indicate current page/section with isActive
- Tooltips - Provide tooltips for icon mode clarity
- Icons - Use consistent icon size (size-4 recommended)
- Mobile Testing - Test sidebar behavior on mobile devices
- Keyboard Access - Ensure all items are keyboard accessible
- Loading States - Use SidebarMenuSkeleton for loading indicators
- Actions - Use SidebarMenuAction for contextual actions
- Separators - Use SidebarSeparator to separate logical sections
Common Use Cases
- Application Navigation - Main app navigation menu
- Dashboard Sidebar - Analytics and data views
- Documentation - Docs navigation with nested sections
- E-commerce - Product categories and filters
- Admin Panels - Management and settings navigation
- Multi-tenant Apps - Organization/workspace switching
- Settings Panels - Configuration sections
- File Browsers - Folder and file navigation
Demo & Storybook
- Demo Page:
/sidebar-demo- Comprehensive examples and use cases - Storybook:
Components/Sidebar- Interactive component playground
Technical Implementation
Variant System
The SidebarMenuButton uses tailwind-variants for styling:
export const sidebarMenuButtonVariants = tv({
base: "flex w-full items-center gap-2 rounded-md p-2 text-sm...",
variants: {
variant: {
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
outline: "bg-background shadow-[0_0_0_1px_var(--sidebar-border)]..."
},
size: {
default: "h-8 text-sm",
sm: "h-7 text-xs",
lg: "h-12 text-sm"
}
},
defaultVariants: {
variant: "default",
size: "default"
}
});
Context System
Uses Svelte 5 context for state management:
setSidebar()- Initializes context in provideruseSidebar()- Accesses context in children- Reactive updates throughout component tree
CSS Custom Properties
--sidebar-width: 16rem
--sidebar-width-mobile: 18rem
--sidebar-width-icon: 3rem