Navigation
Sidebar layout
The frame of an app: navigation down the side, the page beside it.
Installation
npx sinaui add sidebar-layoutCopy src/lib/ui/SidebarLayout.svelte, Dialog.svelte, Tooltip.svelte, floating.ts, glide.ts, Icon.svelte and tokens.css. No other dependencies.
Examples
Library app
Collapse to an icon rail with the button or ⌘B; the icons name themselves in tooltips. The highlight glides to the page you choose.
Catalog
Press ⌘B or Ctrl+B to collapse the sidebar to icons. Make the window narrow for the menu.
<script lang="ts">
import {
Book02Icon,
BookOpen01Icon,
Calendar03Icon,
Home01Icon,
Location01Icon
} from '@hugeicons/core-free-icons';
import SidebarLayout, { type SidebarGroup } from '#lib/ui/SidebarLayout.svelte';
const groups: SidebarGroup[] = [
{
items: [
{ label: 'Home', href: '#home', icon: Home01Icon },
{ label: 'Catalog', href: '#catalog', icon: BookOpen01Icon },
{ label: 'My loans', href: '#loans', icon: Book02Icon },
{ label: 'Bookings', href: '#bookings', icon: Calendar03Icon }
]
},
{
title: 'Reading rooms',
items: [
{ label: 'Baghdad', href: '#baghdad', icon: Location01Icon },
{ label: 'Córdoba', href: '#cordoba', icon: Location01Icon },
{ label: 'Fez', href: '#fez', icon: Location01Icon }
]
}
];
let current = $state('#catalog');
const title = $derived(groups.flatMap((g) => g.items).find((i) => i.href === current)?.label);
</script>
<!-- A framed app for the example; in your app, give the layout the whole window. -->
<div class="frame">
<SidebarLayout label="Library" {groups} {current} onselect={(item) => (current = item.href)}>
{#snippet header(collapsed)}
<span class="brand">
<span class="mark" aria-hidden="true">B</span>
{#if !collapsed}<strong>Bayt al-Ḥikma</strong>{/if}
</span>
{/snippet}
<div class="page">
<h4>{title}</h4>
<p>
Press ⌘B or Ctrl+B to collapse the sidebar to icons. Make the window narrow for the menu.
</p>
</div>
</SidebarLayout>
</div>
<style>
.frame {
inline-size: 100%;
block-size: 28rem;
overflow: hidden;
border-radius: calc(var(--ui-radius) * 1.5);
background: var(--ui-surface);
box-shadow: 0 1px 3px rgb(0 0 0 / 0.06);
}
.brand {
display: flex;
align-items: center;
gap: 0.625rem;
}
.mark {
display: grid;
flex: none;
place-items: center;
inline-size: 1.75rem;
block-size: 1.75rem;
border-radius: calc(var(--ui-radius) * 0.75);
background: var(--ui-accent);
color: var(--ui-on-accent);
font-weight: 600;
}
.page {
padding: 1.25rem 1.5rem;
}
h4 {
margin: 0 0 0.5rem;
font-size: 1.125rem;
}
p {
margin: 0;
color: var(--ui-muted);
}
</style>Props
| Prop | Type | Default |
|---|---|---|
label | string (required) | |
groups | { title?, items: { label, href, icon }[] }[] | |
current | string: href of the page shown | |
collapsed | boolean, bindable | false |
persist | string: remember collapsed under this key | |
onselect | (item) => void | |
header, footer | Snippet |
Accessibility
- A labeled navigation landmark; the current page carries
aria-current="page". - Collapsed, each icon's tooltip is its name, for sighted people and screen readers alike.
- The collapse button says whether the sidebar is open, and its shortcut is announced.
- On narrow screens the same links open in a modal sheet with a close button.