<script lang="ts">
import ArrowLeftIcon from "phosphor-svelte/lib/ArrowLeftIcon";
import ChartBarIcon from "phosphor-svelte/lib/ChartBarIcon";
import CodeIcon from "phosphor-svelte/lib/CodeIcon";
import DatabaseIcon from "phosphor-svelte/lib/DatabaseIcon";
import GlobeIcon from "phosphor-svelte/lib/GlobeIcon";
import HouseIcon from "phosphor-svelte/lib/HouseIcon";
import LockIcon from "phosphor-svelte/lib/LockIcon";
import MagnifyingGlassIcon from "phosphor-svelte/lib/MagnifyingGlassIcon";
import ShieldCheckIcon from "phosphor-svelte/lib/ShieldCheckIcon";
import type { Component } from "svelte";
import * as Sidebar from "kumo-svelte/components/sidebar";
import AccountSwitcher from "./sidebar-account-switcher.svelte";
import DemoShell from "./sidebar-demo-shell.svelte";
import DemoMain from "./sidebar-main.svelte";
interface MenuItem {
active?: boolean;
badge?: string;
class?: string;
icon: Component;
label: string;
onSelect?: () => void;
tooltip?: string;
}
interface SubMenuItem {
badge?: string;
label: string;
}
interface NestedSubMenuItem extends SubMenuItem {
children: SubMenuItem[];
}
interface CollapsibleMenuItem extends MenuItem {
children: Array<NestedSubMenuItem | SubMenuItem>;
}
interface MenuSection {
items: Array<CollapsibleMenuItem | MenuItem>;
label?: string;
}
let surface = $state<"account" | "domain">("account");
function showDomainNavigation() {
surface = "domain";
}
function showAccountNavigation() {
surface = "account";
}
const sidebarNavigation = {
account: {
search: {
label: "Quick search…",
icon: MagnifyingGlassIcon,
tooltip: "Search",
class:
"mb-3 ring ring-kumo-line transition-[margin] duration-250 group-data-[state=collapsed]/sidebar:mb-0 group-data-[state=collapsed]/sidebar:ring-transparent",
} satisfies MenuItem,
sections: [
{
items: [
{ label: "Home", icon: HouseIcon, active: true },
{ label: "Analytics & Logs", icon: ChartBarIcon },
{ label: "Domains", icon: GlobeIcon, onSelect: showDomainNavigation },
],
},
{
label: "Build",
items: [
{
label: "Compute",
icon: CodeIcon,
children: [
{
label: "Workers & Pages",
children: [{ label: "Overview" }, { label: "Workers" }, { label: "Pages" }],
},
{ label: "Durable Objects" },
{ label: "Containers", badge: "Beta" },
],
},
{ label: "Storage", icon: DatabaseIcon },
],
},
{
label: "Protect & Connect",
items: [
{ label: "Security", icon: ShieldCheckIcon },
{ label: "Zero Trust", icon: LockIcon, badge: "Beta" },
],
},
] satisfies MenuSection[],
},
domain: {
sections: [
{
items: [{ label: "Back", icon: ArrowLeftIcon, onSelect: showAccountNavigation }],
},
{
label: "example.com",
items: [
{ label: "Overview", icon: GlobeIcon, active: true },
{ label: "Security", icon: ShieldCheckIcon },
{ label: "SSL/TLS", icon: LockIcon },
{ label: "Analytics", icon: ChartBarIcon },
{ label: "Caching", icon: DatabaseIcon },
],
},
] satisfies MenuSection[],
},
};
function hasChildren(item: CollapsibleMenuItem | MenuItem): item is CollapsibleMenuItem {
return "children" in item;
}
function hasNestedChildren(item: NestedSubMenuItem | SubMenuItem): item is NestedSubMenuItem {
return "children" in item;
}
</script>
{#snippet menuButton(item: MenuItem)}
{@const Icon = item.icon}
<Sidebar.MenuButton active={item.active} onclick={item.onSelect} tooltip={item.tooltip} class={item.class}>
{#snippet icon()}<Icon />{/snippet}
{item.label}
{#if item.badge}<Sidebar.MenuBadge>{item.badge}</Sidebar.MenuBadge>{/if}
</Sidebar.MenuButton>
{/snippet}
{#snippet subMenuButton(item: SubMenuItem)}
<Sidebar.MenuSubButton>
{item.label}
{#if item.badge}<Sidebar.MenuBadge>{item.badge}</Sidebar.MenuBadge>{/if}
</Sidebar.MenuSubButton>
{/snippet}
{#snippet nestedSubMenu(item: NestedSubMenuItem)}
<Sidebar.MenuSubItem>
<Sidebar.Collapsible>
<Sidebar.CollapsibleTrigger>
{#snippet child({ props })}
<Sidebar.MenuSubButton {...props}>
{item.label} <Sidebar.MenuChevron />
</Sidebar.MenuSubButton>
{/snippet}
</Sidebar.CollapsibleTrigger>
<Sidebar.CollapsibleContent>
<Sidebar.MenuSub>
{#each item.children as child (child.label)}
{@render subMenuButton(child)}
{/each}
</Sidebar.MenuSub>
</Sidebar.CollapsibleContent>
</Sidebar.Collapsible>
</Sidebar.MenuSubItem>
{/snippet}
{#snippet collapsibleMenuButton(item: CollapsibleMenuItem)}
<Sidebar.MenuItem>
<Sidebar.Collapsible open>
<Sidebar.CollapsibleTrigger>
{#snippet child({ props })}
{@const Icon = item.icon}
<Sidebar.MenuButton {...props}>
{#snippet icon()}<Icon />{/snippet}
{item.label} <Sidebar.MenuChevron />
</Sidebar.MenuButton>
{/snippet}
</Sidebar.CollapsibleTrigger>
<Sidebar.CollapsibleContent>
<Sidebar.MenuSub>
{#each item.children as child (child.label)}
{#if hasNestedChildren(child)}
{@render nestedSubMenu(child)}
{:else}
{@render subMenuButton(child)}
{/if}
{/each}
</Sidebar.MenuSub>
</Sidebar.CollapsibleContent>
</Sidebar.Collapsible>
</Sidebar.MenuItem>
{/snippet}
{#snippet menuSection(section: MenuSection)}
<Sidebar.Group>
{#if section.label}
<Sidebar.GroupLabel>{section.label}</Sidebar.GroupLabel>
{/if}
<Sidebar.Menu>
{#each section.items as item (item.label)}
{#if hasChildren(item)}
{@render collapsibleMenuButton(item)}
{:else}
{@render menuButton(item)}
{/if}
{/each}
</Sidebar.Menu>
</Sidebar.Group>
{/snippet}
<DemoShell>
<Sidebar.Provider contained defaultOpen peekable class="h-full min-h-0!">
<Sidebar.Root>
<Sidebar.Header>
<AccountSwitcher />
</Sidebar.Header>
<Sidebar.SlidingViews activeKey={surface} direction={surface === "domain" ? "left" : "right"}>
<Sidebar.SlidingView value="account">
<Sidebar.Content>
<Sidebar.Group>
<Sidebar.Menu>
{@render menuButton(sidebarNavigation.account.search)}
</Sidebar.Menu>
</Sidebar.Group>
{#each sidebarNavigation.account.sections as section (section.label ?? section.items[0]?.label)}
{@render menuSection(section)}
{/each}
</Sidebar.Content>
</Sidebar.SlidingView>
<Sidebar.SlidingView value="domain">
<Sidebar.Content>
{#each sidebarNavigation.domain.sections as section (section.label ?? section.items[0]?.label)}
{@render menuSection(section)}
{/each}
</Sidebar.Content>
</Sidebar.SlidingView>
</Sidebar.SlidingViews>
<Sidebar.Footer>
<Sidebar.Trigger />
</Sidebar.Footer>
</Sidebar.Root>
<DemoMain />
</Sidebar.Provider>
</DemoShell>
Import
import * as Sidebar from "kumo-svelte/components/sidebar"; Usage
At minimum you need Sidebar.Provider, Sidebar.Root, Sidebar.Content, Sidebar.Menu,
and Sidebar.MenuButton. Add Sidebar.Header and Sidebar.Footer to pin content above or below the scroll area. Use Sidebar.Group and Sidebar.GroupLabel to organize sections.
<script lang="ts">
import HouseIcon from "phosphor-svelte/lib/HouseIcon";
import * as Sidebar from "kumo-svelte/components/sidebar";
</script>
<Sidebar.Provider defaultOpen>
<Sidebar.Root>
<Sidebar.Content>
<Sidebar.Group>
<Sidebar.GroupLabel>Navigation</Sidebar.GroupLabel>
<Sidebar.Menu>
<Sidebar.MenuButton active>
{#snippet icon()}<HouseIcon />{/snippet}
Home
</Sidebar.MenuButton>
</Sidebar.Menu>
</Sidebar.Group>
</Sidebar.Content>
</Sidebar.Root>
<main class="flex-1"><slot /></main>
</Sidebar.Provider> Examples
Basic
The minimum viable sidebar: just groups, menu buttons, and collapsible sub-menus.
<script lang="ts">
import ChartBarIcon from "phosphor-svelte/lib/ChartBarIcon";
import CodeIcon from "phosphor-svelte/lib/CodeIcon";
import DatabaseIcon from "phosphor-svelte/lib/DatabaseIcon";
import GlobeIcon from "phosphor-svelte/lib/GlobeIcon";
import HouseIcon from "phosphor-svelte/lib/HouseIcon";
import type { Component } from "svelte";
import * as Sidebar from "kumo-svelte/components/sidebar";
import DemoShell from "./sidebar-demo-shell.svelte";
import DemoMain from "./sidebar-main.svelte";
interface MenuItem {
active?: boolean;
icon: Component;
label: string;
}
const overviewItems: MenuItem[] = [
{ label: "Home", icon: HouseIcon, active: true },
{ label: "Analytics", icon: ChartBarIcon },
{ label: "Domains", icon: GlobeIcon },
];
const computeItem = { label: "Compute", icon: CodeIcon } satisfies MenuItem;
const buildItems: MenuItem[] = [{ label: "Storage", icon: DatabaseIcon }];
const workersPagesItems = ["Overview", "Workers", "Pages"];
const computeItems = ["Durable Objects"];
</script>
<DemoShell>
<Sidebar.Provider contained defaultOpen class="h-full min-h-0!">
<Sidebar.Root>
<Sidebar.Content>
<Sidebar.Group>
<Sidebar.GroupLabel>Overview</Sidebar.GroupLabel>
<Sidebar.Menu>
{#each overviewItems as item (item.label)}
{@const Icon = item.icon}
<Sidebar.MenuButton active={item.active}>
{#snippet icon()}<Icon />{/snippet}
{item.label}
</Sidebar.MenuButton>
{/each}
</Sidebar.Menu>
</Sidebar.Group>
<Sidebar.Group>
<Sidebar.GroupLabel>Build</Sidebar.GroupLabel>
<Sidebar.Menu>
<Sidebar.MenuItem>
<Sidebar.Collapsible open>
<Sidebar.CollapsibleTrigger>
{#snippet child({ props })}
{@const Icon = computeItem.icon}
<Sidebar.MenuButton {...props}>
{#snippet icon()}<Icon />{/snippet}
{computeItem.label} <Sidebar.MenuChevron />
</Sidebar.MenuButton>
{/snippet}
</Sidebar.CollapsibleTrigger>
<Sidebar.CollapsibleContent>
<Sidebar.MenuSub>
<Sidebar.MenuSubItem>
<Sidebar.Collapsible>
<Sidebar.CollapsibleTrigger>
{#snippet child({ props })}
<Sidebar.MenuSubButton {...props}>
Workers & Pages <Sidebar.MenuChevron />
</Sidebar.MenuSubButton>
{/snippet}
</Sidebar.CollapsibleTrigger>
<Sidebar.CollapsibleContent>
<Sidebar.MenuSub>
{#each workersPagesItems as label (label)}
<Sidebar.MenuSubButton>{label}</Sidebar.MenuSubButton>
{/each}
</Sidebar.MenuSub>
</Sidebar.CollapsibleContent>
</Sidebar.Collapsible>
</Sidebar.MenuSubItem>
{#each computeItems as label (label)}
<Sidebar.MenuSubButton>{label}</Sidebar.MenuSubButton>
{/each}
</Sidebar.MenuSub>
</Sidebar.CollapsibleContent>
</Sidebar.Collapsible>
</Sidebar.MenuItem>
{#each buildItems as item (item.label)}
{@const Icon = item.icon}
<Sidebar.MenuButton active={item.active}>
{#snippet icon()}<Icon />{/snippet}
{item.label}
</Sidebar.MenuButton>
{/each}
</Sidebar.Menu>
</Sidebar.Group>
</Sidebar.Content>
</Sidebar.Root>
<DemoMain />
</Sidebar.Provider>
</DemoShell>
Toggle & Collapsed State
Use Sidebar.Trigger in the footer or useSidebar().toggleSidebar programmatically. Pass tooltip to show labels on hover when collapsed.
<script lang="ts">
import ChartBarIcon from "phosphor-svelte/lib/ChartBarIcon";
import CodeIcon from "phosphor-svelte/lib/CodeIcon";
import DatabaseIcon from "phosphor-svelte/lib/DatabaseIcon";
import HouseIcon from "phosphor-svelte/lib/HouseIcon";
import type { Component } from "svelte";
import { useSidebar } from "kumo-svelte/components/sidebar";
import * as Sidebar from "kumo-svelte/components/sidebar";
import BrandLogo from "./sidebar-brand-logo.svelte";
import DemoShell from "./sidebar-demo-shell.svelte";
import DemoMain from "./sidebar-main.svelte";
interface MenuItem {
active?: boolean;
icon: Component;
label: string;
tooltip: string;
}
const menuItems: MenuItem[] = [
{ label: "Home", tooltip: "Home", icon: HouseIcon, active: true },
{ label: "Analytics", tooltip: "Analytics", icon: ChartBarIcon },
{ label: "Compute", tooltip: "Compute", icon: CodeIcon },
{ label: "Storage", tooltip: "Storage", icon: DatabaseIcon },
];
</script>
{#snippet toggleButton()}
{@const sidebar = useSidebar("ToggleButton")}
<button
type="button"
onclick={sidebar.toggleSidebar}
class="rounded-lg border border-kumo-hairline bg-kumo-base px-3 py-1.5 text-sm text-kumo-default transition-colors hover:bg-kumo-tint"
>
{sidebar.state === "expanded" ? "Collapse" : "Expand"}
</button>
{/snippet}
<DemoShell>
<Sidebar.Provider contained defaultOpen class="h-full min-h-0!">
<Sidebar.Root>
<Sidebar.Header>
<BrandLogo />
</Sidebar.Header>
<Sidebar.Content>
<Sidebar.Group>
<Sidebar.Menu>
{#each menuItems as item (item.label)}
{@const Icon = item.icon}
<Sidebar.MenuButton active={item.active} tooltip={item.tooltip}>
{#snippet icon()}<Icon />{/snippet}
{item.label}
</Sidebar.MenuButton>
{/each}
</Sidebar.Menu>
</Sidebar.Group>
</Sidebar.Content>
<Sidebar.Footer>
<Sidebar.Trigger />
</Sidebar.Footer>
</Sidebar.Root>
<DemoMain>
{@render toggleButton()}
<p class="text-sm">Click the button or the sidebar trigger to toggle</p>
</DemoMain>
</Sidebar.Provider>
</DemoShell>
Resizable
Drag the edge to resize. Dragging below minWidth collapses; dragging outward from collapsed expands.
<script lang="ts">
import ChartBarIcon from "phosphor-svelte/lib/ChartBarIcon";
import DatabaseIcon from "phosphor-svelte/lib/DatabaseIcon";
import HouseIcon from "phosphor-svelte/lib/HouseIcon";
import type { Component } from "svelte";
import * as Sidebar from "kumo-svelte/components/sidebar";
import BrandLogo from "./sidebar-brand-logo.svelte";
import DemoShell from "./sidebar-demo-shell.svelte";
import DemoMain from "./sidebar-main.svelte";
interface MenuItem {
active?: boolean;
icon: Component;
label: string;
}
const menuItems: MenuItem[] = [
{ label: "Home", icon: HouseIcon, active: true },
{ label: "Analytics", icon: ChartBarIcon },
{ label: "Storage", icon: DatabaseIcon },
];
</script>
<DemoShell>
<Sidebar.Provider contained defaultOpen resizable defaultWidth={240} minWidth={180} maxWidth={400} class="h-full min-h-0!">
<Sidebar.Root>
<Sidebar.Header>
<BrandLogo />
</Sidebar.Header>
<Sidebar.Content>
<Sidebar.Group>
<Sidebar.GroupLabel>Overview</Sidebar.GroupLabel>
<Sidebar.Menu>
{#each menuItems as item (item.label)}
{@const Icon = item.icon}
<Sidebar.MenuButton active={item.active}>
{#snippet icon()}<Icon />{/snippet}
{item.label}
</Sidebar.MenuButton>
{/each}
</Sidebar.Menu>
</Sidebar.Group>
</Sidebar.Content>
<Sidebar.Footer><Sidebar.Trigger /></Sidebar.Footer>
<Sidebar.ResizeHandle />
</Sidebar.Root>
<DemoMain><p class="text-sm">Drag the sidebar edge to resize</p></DemoMain>
</Sidebar.Provider>
</DemoShell>
Right Side
Use side="right" for a sidebar on the right edge. Place main content before Sidebar.Root in the DOM.
<script lang="ts">
import BellIcon from "phosphor-svelte/lib/BellIcon";
import ChartBarIcon from "phosphor-svelte/lib/ChartBarIcon";
import GearIcon from "phosphor-svelte/lib/GearIcon";
import type { Component } from "svelte";
import * as Sidebar from "kumo-svelte/components/sidebar";
import DemoShell from "./sidebar-demo-shell.svelte";
import DemoMain from "./sidebar-main.svelte";
interface MenuItem {
active?: boolean;
icon: Component;
label: string;
}
const detailItems: MenuItem[] = [
{ label: "Properties", icon: GearIcon, active: true },
{ label: "Metrics", icon: ChartBarIcon },
{ label: "Alerts", icon: BellIcon },
];
</script>
<DemoShell>
<Sidebar.Provider contained defaultOpen side="right" class="h-full min-h-0!">
<DemoMain />
<Sidebar.Root>
<Sidebar.Content>
<Sidebar.Group>
<Sidebar.GroupLabel>Details</Sidebar.GroupLabel>
<Sidebar.Menu>
{#each detailItems as item (item.label)}
{@const Icon = item.icon}
<Sidebar.MenuButton active={item.active}>
{#snippet icon()}<Icon />{/snippet}
{item.label}
</Sidebar.MenuButton>
{/each}
</Sidebar.Menu>
</Sidebar.Group>
</Sidebar.Content>
</Sidebar.Root>
</Sidebar.Provider>
</DemoShell>
Peeking
Set peekable to temporarily expand the collapsed sidebar on hover or focus.
<script lang="ts">
import ChartBarIcon from "phosphor-svelte/lib/ChartBarIcon";
import CodeIcon from "phosphor-svelte/lib/CodeIcon";
import DatabaseIcon from "phosphor-svelte/lib/DatabaseIcon";
import HouseIcon from "phosphor-svelte/lib/HouseIcon";
import type { Component } from "svelte";
import { useSidebar } from "kumo-svelte/components/sidebar";
import * as Sidebar from "kumo-svelte/components/sidebar";
import BrandLogo from "./sidebar-brand-logo.svelte";
import DemoShell from "./sidebar-demo-shell.svelte";
import DemoMain from "./sidebar-main.svelte";
interface MenuItem {
active?: boolean;
icon: Component;
label: string;
tooltip: string;
}
const menuItems: MenuItem[] = [
{ label: "Home", tooltip: "Home", icon: HouseIcon, active: true },
{ label: "Analytics", tooltip: "Analytics", icon: ChartBarIcon },
{ label: "Compute", tooltip: "Compute", icon: CodeIcon },
{ label: "Storage", tooltip: "Storage", icon: DatabaseIcon },
];
const labels = {
collapsed: "Collapsed",
expanded: "Expanded",
peeking: "Peeking",
};
</script>
{#snippet peekStateIndicator()}
{@const sidebar = useSidebar("PeekStateIndicator")}
<div class="flex flex-col items-center gap-2">
<span class="font-medium text-kumo-default">State: {labels[sidebar.state]}</span>
<p>Collapse, then hover the sidebar to peek</p>
</div>
{/snippet}
<DemoShell>
<Sidebar.Provider contained defaultOpen peekable class="h-full min-h-0!">
<Sidebar.Root>
<Sidebar.Header>
<BrandLogo />
</Sidebar.Header>
<Sidebar.Content>
<Sidebar.Group>
<Sidebar.Menu>
{#each menuItems as item (item.label)}
{@const Icon = item.icon}
<Sidebar.MenuButton active={item.active} tooltip={item.tooltip}>
{#snippet icon()}<Icon />{/snippet}
{item.label}
</Sidebar.MenuButton>
{/each}
</Sidebar.Menu>
</Sidebar.Group>
</Sidebar.Content>
<Sidebar.Footer>
<Sidebar.Trigger />
</Sidebar.Footer>
</Sidebar.Root>
<DemoMain>
{@render peekStateIndicator()}
</DemoMain>
</Sidebar.Provider>
</DemoShell>
Auto Scroll
Set autoScrollOnOpen on a nested Sidebar.Collapsible to keep newly revealed items in view.
<script lang="ts">
import ChartBarIcon from "phosphor-svelte/lib/ChartBarIcon";
import CodeIcon from "phosphor-svelte/lib/CodeIcon";
import CubeIcon from "phosphor-svelte/lib/CubeIcon";
import DatabaseIcon from "phosphor-svelte/lib/DatabaseIcon";
import GearIcon from "phosphor-svelte/lib/GearIcon";
import GlobeIcon from "phosphor-svelte/lib/GlobeIcon";
import HouseIcon from "phosphor-svelte/lib/HouseIcon";
import LockIcon from "phosphor-svelte/lib/LockIcon";
import ShieldCheckIcon from "phosphor-svelte/lib/ShieldCheckIcon";
import type { Component } from "svelte";
import * as Sidebar from "kumo-svelte/components/sidebar";
import BrandLogo from "./sidebar-brand-logo.svelte";
import DemoMain from "./sidebar-main.svelte";
interface MenuItem {
active?: boolean;
badge?: string;
icon: Component;
label: string;
}
interface Section {
items: MenuItem[];
label: string;
}
const sections: Section[] = [
{
label: "Overview",
items: [
{ label: "Home", icon: HouseIcon, active: true },
{ label: "Analytics", icon: ChartBarIcon },
{ label: "Domains", icon: GlobeIcon },
],
},
{
label: "Platform",
items: [
{ label: "Storage", icon: DatabaseIcon },
{ label: "Security", icon: ShieldCheckIcon },
{ label: "Zero Trust", icon: LockIcon },
{ label: "Settings", icon: GearIcon },
],
},
];
const workerItems = ["Overview", "Deployments", "Observability", "Settings"];
const containersItem = { label: "Containers", icon: CubeIcon, badge: "Beta" } satisfies MenuItem;
</script>
<div class="relative h-[420px] w-full overflow-hidden rounded-lg border border-kumo-line bg-kumo-base">
<Sidebar.Provider contained defaultOpen class="h-full min-h-0!">
<Sidebar.Root>
<Sidebar.Header>
<BrandLogo />
</Sidebar.Header>
<Sidebar.Content>
{#each sections as section (section.label)}
<Sidebar.Group>
<Sidebar.GroupLabel>{section.label}</Sidebar.GroupLabel>
<Sidebar.Menu>
{#each section.items as item (item.label)}
{@const Icon = item.icon}
<Sidebar.MenuButton active={item.active}>
{#snippet icon()}<Icon />{/snippet}
{item.label}
</Sidebar.MenuButton>
{/each}
</Sidebar.Menu>
</Sidebar.Group>
{/each}
<Sidebar.Group>
<Sidebar.GroupLabel>Build</Sidebar.GroupLabel>
<Sidebar.Menu>
<Sidebar.MenuItem>
<Sidebar.Collapsible autoScrollOnOpen>
<Sidebar.CollapsibleTrigger>
{#snippet child({ props })}
<Sidebar.MenuButton {...props}>
{#snippet icon()}<CodeIcon />{/snippet}
Workers <Sidebar.MenuChevron />
</Sidebar.MenuButton>
{/snippet}
</Sidebar.CollapsibleTrigger>
<Sidebar.CollapsibleContent>
<Sidebar.MenuSub>
{#each workerItems as label (label)}
<Sidebar.MenuSubButton>{label}</Sidebar.MenuSubButton>
{/each}
</Sidebar.MenuSub>
</Sidebar.CollapsibleContent>
</Sidebar.Collapsible>
</Sidebar.MenuItem>
<Sidebar.MenuButton>
{#snippet icon()}<CubeIcon />{/snippet}
{containersItem.label}
<Sidebar.MenuBadge>{containersItem.badge}</Sidebar.MenuBadge>
</Sidebar.MenuButton>
</Sidebar.Menu>
</Sidebar.Group>
</Sidebar.Content>
<Sidebar.Footer>
<Sidebar.Trigger />
</Sidebar.Footer>
</Sidebar.Root>
<DemoMain>
<p>Open Workers near the bottom of the list</p>
</DemoMain>
</Sidebar.Provider>
</div>
Sliding Views
Use Sidebar.SlidingViews and Sidebar.SlidingView to animate between navigation surfaces while keeping inactive views hidden from assistive technology and keyboard focus.
<script lang="ts">
import ArrowsLeftRightIcon from "phosphor-svelte/lib/ArrowsLeftRightIcon";
import ChartBarIcon from "phosphor-svelte/lib/ChartBarIcon";
import DatabaseIcon from "phosphor-svelte/lib/DatabaseIcon";
import GearIcon from "phosphor-svelte/lib/GearIcon";
import GlobeIcon from "phosphor-svelte/lib/GlobeIcon";
import HouseIcon from "phosphor-svelte/lib/HouseIcon";
import LockIcon from "phosphor-svelte/lib/LockIcon";
import ShieldCheckIcon from "phosphor-svelte/lib/ShieldCheckIcon";
import UserIcon from "phosphor-svelte/lib/UserIcon";
import type { Component } from "svelte";
import * as Sidebar from "kumo-svelte/components/sidebar";
import DemoShell from "./sidebar-demo-shell.svelte";
import DemoMain from "./sidebar-main.svelte";
type SidebarSurface = "account" | "zone";
interface MenuItem {
active?: boolean;
icon: Component;
label: string;
}
const accountItems: MenuItem[] = [
{ label: "Home", icon: HouseIcon, active: true },
{ label: "Members", icon: UserIcon },
{ label: "Analytics", icon: ChartBarIcon },
{ label: "Settings", icon: GearIcon },
];
const zoneItems: MenuItem[] = [
{ label: "Overview", icon: GlobeIcon, active: true },
{ label: "Security", icon: ShieldCheckIcon },
{ label: "SSL/TLS", icon: LockIcon },
{ label: "Caching", icon: DatabaseIcon },
];
let surface = $state<SidebarSurface>("account");
</script>
<DemoShell>
<Sidebar.Provider contained defaultOpen class="h-full min-h-0!">
<Sidebar.Root>
<Sidebar.Header>
<button
type="button"
onclick={() => (surface = surface === "account" ? "zone" : "account")}
class="flex w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium text-kumo-default transition-colors hover:bg-kumo-tint"
>
<ArrowsLeftRightIcon class="size-4 shrink-0 text-kumo-brand" />
<span class="flex-1 text-left font-semibold text-kumo-strong">
{surface === "account" ? "Account Nav" : "Zone Nav"}
</span>
</button>
</Sidebar.Header>
<Sidebar.SlidingViews activeKey={surface} direction={surface === "zone" ? "left" : "right"}>
<Sidebar.SlidingView value="account">
<Sidebar.Content>
<Sidebar.Group>
<Sidebar.GroupLabel>Account</Sidebar.GroupLabel>
<Sidebar.Menu>
{#each accountItems as item (item.label)}
{@const Icon = item.icon}
<Sidebar.MenuButton active={item.active}>
{#snippet icon()}<Icon />{/snippet}
{item.label}
</Sidebar.MenuButton>
{/each}
</Sidebar.Menu>
</Sidebar.Group>
</Sidebar.Content>
</Sidebar.SlidingView>
<Sidebar.SlidingView value="zone">
<Sidebar.Content>
<Sidebar.Group>
<Sidebar.GroupLabel>Zone</Sidebar.GroupLabel>
<Sidebar.Menu>
{#each zoneItems as item (item.label)}
{@const Icon = item.icon}
<Sidebar.MenuButton active={item.active}>
{#snippet icon()}<Icon />{/snippet}
{item.label}
</Sidebar.MenuButton>
{/each}
</Sidebar.Menu>
</Sidebar.Group>
</Sidebar.Content>
</Sidebar.SlidingView>
</Sidebar.SlidingViews>
</Sidebar.Root>
<DemoMain>
<div class="flex flex-col items-center gap-2">
<p class="font-medium text-kumo-default">
Active: {surface === "account" ? "Account" : "Zone"} surface
</p>
<p>Click the header button to slide between views</p>
</div>
</DemoMain>
</Sidebar.Provider>
</DemoShell>
Full Example
Kitchen sink: account switcher, sliding views, badges, nested collapsible sub-menus, and a footer action.
<script lang="ts">
import ArrowLeftIcon from "phosphor-svelte/lib/ArrowLeftIcon";
import ChartBarIcon from "phosphor-svelte/lib/ChartBarIcon";
import CodeIcon from "phosphor-svelte/lib/CodeIcon";
import DatabaseIcon from "phosphor-svelte/lib/DatabaseIcon";
import GlobeIcon from "phosphor-svelte/lib/GlobeIcon";
import HouseIcon from "phosphor-svelte/lib/HouseIcon";
import LockIcon from "phosphor-svelte/lib/LockIcon";
import MagnifyingGlassIcon from "phosphor-svelte/lib/MagnifyingGlassIcon";
import ShieldCheckIcon from "phosphor-svelte/lib/ShieldCheckIcon";
import type { Component } from "svelte";
import * as Sidebar from "kumo-svelte/components/sidebar";
import AccountSwitcher from "./sidebar-account-switcher.svelte";
import DemoShell from "./sidebar-demo-shell.svelte";
import DemoMain from "./sidebar-main.svelte";
interface MenuItem {
active?: boolean;
badge?: string;
class?: string;
icon: Component;
label: string;
onSelect?: () => void;
tooltip?: string;
}
interface SubMenuItem {
badge?: string;
label: string;
}
interface NestedSubMenuItem extends SubMenuItem {
children: SubMenuItem[];
}
interface CollapsibleMenuItem extends MenuItem {
children: Array<NestedSubMenuItem | SubMenuItem>;
}
interface MenuSection {
items: Array<CollapsibleMenuItem | MenuItem>;
label?: string;
}
let surface = $state<"account" | "domain">("account");
function showDomainNavigation() {
surface = "domain";
}
function showAccountNavigation() {
surface = "account";
}
const sidebarNavigation = {
account: {
search: {
label: "Quick search…",
icon: MagnifyingGlassIcon,
tooltip: "Search",
class:
"mb-3 ring ring-kumo-line transition-[margin] duration-250 group-data-[state=collapsed]/sidebar:mb-0 group-data-[state=collapsed]/sidebar:ring-transparent",
} satisfies MenuItem,
sections: [
{
items: [
{ label: "Home", icon: HouseIcon, active: true },
{ label: "Analytics & Logs", icon: ChartBarIcon },
{ label: "Domains", icon: GlobeIcon, onSelect: showDomainNavigation },
],
},
{
label: "Build",
items: [
{
label: "Compute",
icon: CodeIcon,
children: [
{
label: "Workers & Pages",
children: [{ label: "Overview" }, { label: "Workers" }, { label: "Pages" }],
},
{ label: "Durable Objects" },
{ label: "Containers", badge: "Beta" },
],
},
{ label: "Storage", icon: DatabaseIcon },
],
},
{
label: "Protect & Connect",
items: [
{ label: "Security", icon: ShieldCheckIcon },
{ label: "Zero Trust", icon: LockIcon, badge: "Beta" },
],
},
] satisfies MenuSection[],
},
domain: {
sections: [
{
items: [{ label: "Back", icon: ArrowLeftIcon, onSelect: showAccountNavigation }],
},
{
label: "example.com",
items: [
{ label: "Overview", icon: GlobeIcon, active: true },
{ label: "Security", icon: ShieldCheckIcon },
{ label: "SSL/TLS", icon: LockIcon },
{ label: "Analytics", icon: ChartBarIcon },
{ label: "Caching", icon: DatabaseIcon },
],
},
] satisfies MenuSection[],
},
};
function hasChildren(item: CollapsibleMenuItem | MenuItem): item is CollapsibleMenuItem {
return "children" in item;
}
function hasNestedChildren(item: NestedSubMenuItem | SubMenuItem): item is NestedSubMenuItem {
return "children" in item;
}
</script>
{#snippet menuButton(item: MenuItem)}
{@const Icon = item.icon}
<Sidebar.MenuButton active={item.active} onclick={item.onSelect} tooltip={item.tooltip} class={item.class}>
{#snippet icon()}<Icon />{/snippet}
{item.label}
{#if item.badge}<Sidebar.MenuBadge>{item.badge}</Sidebar.MenuBadge>{/if}
</Sidebar.MenuButton>
{/snippet}
{#snippet subMenuButton(item: SubMenuItem)}
<Sidebar.MenuSubButton>
{item.label}
{#if item.badge}<Sidebar.MenuBadge>{item.badge}</Sidebar.MenuBadge>{/if}
</Sidebar.MenuSubButton>
{/snippet}
{#snippet nestedSubMenu(item: NestedSubMenuItem)}
<Sidebar.MenuSubItem>
<Sidebar.Collapsible>
<Sidebar.CollapsibleTrigger>
{#snippet child({ props })}
<Sidebar.MenuSubButton {...props}>
{item.label} <Sidebar.MenuChevron />
</Sidebar.MenuSubButton>
{/snippet}
</Sidebar.CollapsibleTrigger>
<Sidebar.CollapsibleContent>
<Sidebar.MenuSub>
{#each item.children as child (child.label)}
{@render subMenuButton(child)}
{/each}
</Sidebar.MenuSub>
</Sidebar.CollapsibleContent>
</Sidebar.Collapsible>
</Sidebar.MenuSubItem>
{/snippet}
{#snippet collapsibleMenuButton(item: CollapsibleMenuItem)}
<Sidebar.MenuItem>
<Sidebar.Collapsible open>
<Sidebar.CollapsibleTrigger>
{#snippet child({ props })}
{@const Icon = item.icon}
<Sidebar.MenuButton {...props}>
{#snippet icon()}<Icon />{/snippet}
{item.label} <Sidebar.MenuChevron />
</Sidebar.MenuButton>
{/snippet}
</Sidebar.CollapsibleTrigger>
<Sidebar.CollapsibleContent>
<Sidebar.MenuSub>
{#each item.children as child (child.label)}
{#if hasNestedChildren(child)}
{@render nestedSubMenu(child)}
{:else}
{@render subMenuButton(child)}
{/if}
{/each}
</Sidebar.MenuSub>
</Sidebar.CollapsibleContent>
</Sidebar.Collapsible>
</Sidebar.MenuItem>
{/snippet}
{#snippet menuSection(section: MenuSection)}
<Sidebar.Group>
{#if section.label}
<Sidebar.GroupLabel>{section.label}</Sidebar.GroupLabel>
{/if}
<Sidebar.Menu>
{#each section.items as item (item.label)}
{#if hasChildren(item)}
{@render collapsibleMenuButton(item)}
{:else}
{@render menuButton(item)}
{/if}
{/each}
</Sidebar.Menu>
</Sidebar.Group>
{/snippet}
<DemoShell>
<Sidebar.Provider contained defaultOpen peekable class="h-full min-h-0!">
<Sidebar.Root>
<Sidebar.Header>
<AccountSwitcher />
</Sidebar.Header>
<Sidebar.SlidingViews activeKey={surface} direction={surface === "domain" ? "left" : "right"}>
<Sidebar.SlidingView value="account">
<Sidebar.Content>
<Sidebar.Group>
<Sidebar.Menu>
{@render menuButton(sidebarNavigation.account.search)}
</Sidebar.Menu>
</Sidebar.Group>
{#each sidebarNavigation.account.sections as section (section.label ?? section.items[0]?.label)}
{@render menuSection(section)}
{/each}
</Sidebar.Content>
</Sidebar.SlidingView>
<Sidebar.SlidingView value="domain">
<Sidebar.Content>
{#each sidebarNavigation.domain.sections as section (section.label ?? section.items[0]?.label)}
{@render menuSection(section)}
{/each}
</Sidebar.Content>
</Sidebar.SlidingView>
</Sidebar.SlidingViews>
<Sidebar.Footer>
<Sidebar.Trigger />
</Sidebar.Footer>
</Sidebar.Root>
<DemoMain />
</Sidebar.Provider>
</DemoShell>
Mobile
Use mobileBreakpoint to choose when the sidebar switches to a mobile drawer. This demo sets a high breakpoint to force the mobile variant.
<script lang="ts">
import ChartBarIcon from "phosphor-svelte/lib/ChartBarIcon";
import CodeIcon from "phosphor-svelte/lib/CodeIcon";
import DatabaseIcon from "phosphor-svelte/lib/DatabaseIcon";
import GlobeIcon from "phosphor-svelte/lib/GlobeIcon";
import HouseIcon from "phosphor-svelte/lib/HouseIcon";
import type { Component } from "svelte";
import { useSidebar } from "kumo-svelte/components/sidebar";
import * as Sidebar from "kumo-svelte/components/sidebar";
import BrandLogo from "./sidebar-brand-logo.svelte";
import DemoMain from "./sidebar-main.svelte";
interface MenuItem {
active?: boolean;
icon: Component;
label: string;
}
interface Section {
items: MenuItem[];
label: string;
}
const sections: Section[] = [
{
label: "Overview",
items: [
{ label: "Home", icon: HouseIcon, active: true },
{ label: "Analytics", icon: ChartBarIcon },
{ label: "Domains", icon: GlobeIcon },
],
},
{
label: "Build",
items: [
{ label: "Compute", icon: CodeIcon },
{ label: "Storage", icon: DatabaseIcon },
],
},
];
</script>
{#snippet mobileToggleButton()}
{@const sidebar = useSidebar("MobileToggleButton")}
<button
type="button"
onclick={sidebar.toggleSidebar}
class="cursor-pointer rounded-lg border border-kumo-line bg-kumo-base px-3 py-1.5 text-base text-kumo-default transition-colors hover:bg-kumo-tint"
>
{sidebar.openMobile ? "Close sidebar" : "Open sidebar"}
</button>
{/snippet}
<div class="relative h-[540px] w-full overflow-hidden rounded-lg border border-kumo-line bg-kumo-base">
<Sidebar.Provider contained mobileBreakpoint={9999} class="h-full">
<Sidebar.Root>
<Sidebar.Header>
<BrandLogo />
</Sidebar.Header>
<Sidebar.Content>
{#each sections as section (section.label)}
<Sidebar.Group>
<Sidebar.GroupLabel>{section.label}</Sidebar.GroupLabel>
<Sidebar.Menu>
{#each section.items as item (item.label)}
{@const Icon = item.icon}
<Sidebar.MenuButton active={item.active}>
{#snippet icon()}<Icon />{/snippet}
{item.label}
</Sidebar.MenuButton>
{/each}
</Sidebar.Menu>
</Sidebar.Group>
{/each}
</Sidebar.Content>
<Sidebar.Footer>
<Sidebar.Trigger />
</Sidebar.Footer>
</Sidebar.Root>
<DemoMain>
{@render mobileToggleButton()}
<p>Click the button to open the mobile sidebar</p>
<p class="text-sm text-kumo-subtle">Press Escape or click the backdrop to close</p>
</DemoMain>
</Sidebar.Provider>
</div>
Collapsible Groups
Add collapsible to a Sidebar.Group and wrap the menu in Sidebar.GroupContent to enable animated expand/collapse via the group label. This is a Svelte-specific convenience on top of the upstream Sidebar primitives.
<script lang="ts">
import ChartBarIcon from "phosphor-svelte/lib/ChartBarIcon";
import CodeIcon from "phosphor-svelte/lib/CodeIcon";
import DatabaseIcon from "phosphor-svelte/lib/DatabaseIcon";
import GlobeIcon from "phosphor-svelte/lib/GlobeIcon";
import HouseIcon from "phosphor-svelte/lib/HouseIcon";
import LockIcon from "phosphor-svelte/lib/LockIcon";
import ShieldCheckIcon from "phosphor-svelte/lib/ShieldCheckIcon";
import type { Component } from "svelte";
import * as Sidebar from "kumo-svelte/components/sidebar";
import DemoShell from "./sidebar-demo-shell.svelte";
import DemoMain from "./sidebar-main.svelte";
interface MenuItem {
active?: boolean;
icon: Component;
label: string;
}
const overviewItems: MenuItem[] = [
{ label: "Home", icon: HouseIcon, active: true },
{ label: "Analytics", icon: ChartBarIcon },
{ label: "Domains", icon: GlobeIcon },
];
const buildItems: MenuItem[] = [
{ label: "Compute", icon: CodeIcon },
{ label: "Storage", icon: DatabaseIcon },
];
const protectItems: MenuItem[] = [
{ label: "Security", icon: ShieldCheckIcon },
{ label: "Zero Trust", icon: LockIcon },
];
</script>
<DemoShell>
<Sidebar.Provider contained defaultOpen class="h-full min-h-0!">
<Sidebar.Root>
<Sidebar.Content>
<Sidebar.Group>
<Sidebar.GroupLabel>Overview</Sidebar.GroupLabel>
<Sidebar.Menu>
{#each overviewItems as item (item.label)}
{@const Icon = item.icon}
<Sidebar.MenuButton active={item.active}>
{#snippet icon()}<Icon />{/snippet}
{item.label}
</Sidebar.MenuButton>
{/each}
</Sidebar.Menu>
</Sidebar.Group>
<Sidebar.Group collapsible defaultOpen>
<Sidebar.GroupLabel>Build</Sidebar.GroupLabel>
<Sidebar.GroupContent>
<Sidebar.Menu>
{#each buildItems as item (item.label)}
{@const Icon = item.icon}
<Sidebar.MenuButton>
{#snippet icon()}<Icon />{/snippet}
{item.label}
</Sidebar.MenuButton>
{/each}
</Sidebar.Menu>
</Sidebar.GroupContent>
</Sidebar.Group>
<Sidebar.Group collapsible defaultOpen={false}>
<Sidebar.GroupLabel>Protect & Connect</Sidebar.GroupLabel>
<Sidebar.GroupContent>
<Sidebar.Menu>
{#each protectItems as item (item.label)}
{@const Icon = item.icon}
<Sidebar.MenuButton>
{#snippet icon()}<Icon />{/snippet}
{item.label}
</Sidebar.MenuButton>
{/each}
</Sidebar.Menu>
</Sidebar.GroupContent>
</Sidebar.Group>
</Sidebar.Content>
</Sidebar.Root>
<DemoMain />
</Sidebar.Provider>
</DemoShell>
API Reference
Sidebar
| Prop | Type | Default |
|---|---|---|
| children* | Snippet | — |
| class | string | — |
| contentClassName | string | — |
| ref | HTMLElement | null | null |
Sidebar.Collapsible
| Prop | Type | Default |
|---|---|---|
| autoScrollOnOpen | boolean | false |
| children* | Snippet | — |
| class | string | — |
| defaultOpen | boolean | false |
| disabled | boolean | false |
| id | string | — |
| open | boolean | defaultOpen |
| onOpenChange | (open: boolean) => void | — |
| onOpenChangeComplete | (open: boolean) => void | — |
Sidebar.CollapsibleContent
| Prop | Type | Default |
|---|---|---|
| class | string | — |
Sidebar.CollapsibleTrigger
| Prop | Type | Default |
|---|---|---|
| class | string | — |
Sidebar.Content
No component-specific props. This component accepts child content or standard forwarded attributes.
Sidebar.Footer
No component-specific props. This component accepts child content or standard forwarded attributes.
Sidebar.Group
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
| collapsible | boolean | false |
| defaultOpen | boolean | true |
| onOpenChange | (open: boolean) => void | — |
| open | boolean | defaultOpen |
Sidebar.GroupContent
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
Sidebar.GroupLabel
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
Sidebar.Header
No component-specific props. This component accepts child content or standard forwarded attributes.
Sidebar.Input
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
| placeholder | string | "Search..." |
| shortcut | string | — |
Sidebar.Menu
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
Sidebar.MenuAction
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
Sidebar.MenuBadge
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
Sidebar.MenuButton
| Prop | Type | Default | Description |
|---|---|---|---|
| active | boolean | false | — |
| child | Snippet<[{ props: ChildProps }]> | — | Render a custom interactive element. Spread `props` onto that element. |
| children | Snippet | — | — |
| class | string | — | — |
| href | string | — | — |
| icon | Snippet | — | — |
| linkProps | Omit<HTMLAnchorAttributes, "children" | "class" | "href"> | — | — |
| size | SidebarMenuButtonSize | "base" | — |
| tooltip | string | — | — |
Sidebar.MenuChevron
| Prop | Type | Default |
|---|---|---|
| class | string | — |
Sidebar.MenuItem
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
Sidebar.MenuSub
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
Sidebar.MenuSubButton
| Prop | Type | Default | Description |
|---|---|---|---|
| active | boolean | false | — |
| child | Snippet<[{ props: ChildProps }]> | — | Render a custom interactive element. Spread `props` onto that element. |
| children | Snippet | — | — |
| class | string | — | — |
| href | string | — | — |
| linkProps | Omit<HTMLAnchorAttributes, "children" | "class" | "href"> | — | — |
Sidebar.MenuSubItem
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
Sidebar.Provider
| Prop | Type | Default |
|---|---|---|
| animationDuration | number | KUMO_SIDEBAR_STYLING.animation.duration |
| children* | Snippet | — |
| class | string | — |
| collapsible | SidebarCollapsible | KUMO_SIDEBAR_DEFAULT_VARIANTS.collapsible |
| contained | boolean | false |
| defaultOpen | boolean | true |
| defaultWidth | number | DEFAULT_WIDTH_PX |
| maxWidth | number | MAX_WIDTH_PX |
| minWidth | number | MIN_WIDTH_PX |
| mobileBreakpoint | number | KUMO_SIDEBAR_STYLING.mobile.breakpoint |
| onOpenChange | (open: boolean) => void | — |
| onWidthChange | (width: number) => void | — |
| open | boolean | defaultOpen |
| peekable | boolean | false |
| resizable | boolean | false |
| side | SidebarSide | KUMO_SIDEBAR_DEFAULT_VARIANTS.side |
| style | string | — |
| variant | SidebarVariant | KUMO_SIDEBAR_DEFAULT_VARIANTS.variant |
| width | number | defaultWidth |
Sidebar.Rail
| Prop | Type | Default |
|---|---|---|
| class | string | — |
Sidebar.ResizeHandle
| Prop | Type | Default |
|---|---|---|
| class | string | — |
Sidebar.Separator
| Prop | Type | Default |
|---|---|---|
| class | string | — |
Sidebar.SlidingView
| Prop | Type | Default | Description |
|---|---|---|---|
| children | Snippet | — | — |
| class | string | — | — |
| ref | HTMLDivElement | null | null | — |
| value* | string | — | Unique key matching this view. Must correspond to `activeKey` on `SidebarSlidingViews`. |
Sidebar.SlidingViews
| Prop | Type | Default | Description |
|---|---|---|---|
| activeKey* | string | — | Key of the currently active view. Must match a child `SidebarSlidingView` value. |
| children* | Snippet | — | — |
| class | string | — | — |
| direction | "left" | "right" | "left" | Slide direction for the transition. - `left`: new view slides in from the right - `right`: new view slides in from the left |
| ref | HTMLDivElement | null | null | — |
Sidebar.Trigger
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
Icon Snippets
For menu button icons, place a named icon snippet inside Sidebar.MenuButton so the icon stays colocated with its label.
<Sidebar.MenuButton active>
{#snippet icon()}<HouseIcon />{/snippet}
Home
</Sidebar.MenuButton> Child Snippets
Sidebar.MenuButton and Sidebar.MenuSubButton render a button or link by default. Use a child snippet when you need to render a custom interactive element, and forward the provided props to that element. This follows the Bits UI child snippet pattern.
<Sidebar.MenuButton>
{#snippet child({ props })}
<a href="/home" {...props}>
<HouseIcon />
<span>Home</span>
</a>
{/snippet}
</Sidebar.MenuButton>