CwSideNav

Collapsible side navigation with open, mini and hidden modes. Supports left/right positioning, groups, separators, per-item href/goto, trailing counters and custom trailing content.

  • Mini: icon-only rail. Each item's label appears as a tooltip on hover and on keyboard focus, and the active item has a leading accent bar.
  • Hidden: the nav is removed from the tab order, so keyboard users never land on links they can't see.
  • Phones (≤ 640px): an open nav slides over the page with a scrim. Escape, the scrim or choosing an item closes it and focus returns to where it was. Resize the window to try it.
Mode
Side

Active: home

Mode: open

Side: left

Last event: none yet

CwSideNav example Copy code
<CwSideNav
	items={items}
	bind:mode={mode}
	side="left"
	onselect={(item) => (activeId = item.id)}>
	{#snippet itemTrailing(item)}
		{#if item.id === "settings"}
			<CwButton size="sm" variant="ghost">Open</CwButton>
		{/if}
	{/snippet}
</CwSideNav>
API

Usage and API reference

CwSideNav is the primary navigation rail. It has three modes (open, mini icon rail, hidden) and, with `responsive`, picks one per breakpoint. On phones an open nav slides over the page with a scrim and traps focus until it closes.

How to think about it

  1. Describe items as data Each item has an `id`, `label`, optional 16×16 `icon` path, and either `href` or `goto`. Items with the same `group` are listed under a group heading.
  2. Turn on `responsive` Open at ≥1024px, mini from 640px, hidden below 640px. The mode only changes when a breakpoint is crossed, so a nav the user opened stays open while they scroll.
  3. Mark the active item Set `active: true` on the current route. It gets `aria-current="page"` and a leading accent bar, so the state never relies on colour alone.
  4. Provide mini-mode variants `headerMini` and `footerMini` render in the icon rail. In mini mode item labels appear as tooltips on hover and keyboard focus.

Props and callbacks

APITypeDetails
items Required
CwSideNavItem[]Navigation entries.
mode

Default: 'open'

'open' | 'mini' | 'hidden'Bindable display mode.
side

Default: 'left'

'left' | 'right'Which edge the nav is attached to.
responsive

Default: false

booleanChoose the mode from the viewport width.
onselect
(item: CwSideNavItem) => voidCalled when an item is activated.
header / headerMini
SnippetLogo area for open and mini modes.
aboveContent
SnippetContent above the items, such as a search field.
itemTrailing
Snippet<[CwSideNavItem]>Custom content on the right of each item in open mode.
footer / footerMini
SnippetBottom area for open and mini modes. Only `footerMini` shows in the icon rail.
ariaLabel
stringAccessible name for the navigation landmark.
labels
{ close?; collapse?; expand? }Accessible labels for the built-in toggle buttons.

Copy-paste examples

Each snippet is a complete, working example you can paste into a Svelte 5 component.

Responsive navigation
Responsive navigation Copy code
<script lang="ts">
	import { CwSideNav } from '@cropwatchdevelopment/cwui';
	import type { CwSideNavItem, CwSideNavMode } from '@cropwatchdevelopment/cwui';
	import { page } from '$app/state';

	let mode = $state<CwSideNavMode>('open');

	const items: CwSideNavItem[] = [
		{ id: 'dashboard', label: 'Dashboard', href: '/', group: 'Monitor' },
		{ id: 'devices', label: 'Devices', href: '/devices', group: 'Monitor' },
		{ id: 'reports', label: 'Reports', href: '/reports', group: 'Analyse' }
	];

	const withActive = $derived(items.map((item) => ({ ...item, active: item.href === page.url.pathname })));
</script>

<CwSideNav items={withActive} bind:mode responsive ariaLabel="Main">
	{#snippet header()}<strong>CropWatch</strong>{/snippet}
</CwSideNav>