Nav Component
Flexible, accessible navigation with dropdown support, mobile burger menu, and keyboard navigation.
- π― Responsive with automatic mobile burger menu (default breakpoint < 768px)
- β¨οΈ Full keyboard navigation (Enter/Space/Arrows/Escape)
- βΏ Proper ARIA attributes and focus management
- π¨ Customizable via CSS variables and props
Basic Usage
<script lang="ts">
import { Nav } from '$lib'
import { page } from '$app/state'
const routes: string[] = ['/', '/about', '/contact', '/blog']
const link_props = { onclick: (event: MouseEvent) => event.preventDefault() }
</script>
<Nav {routes} {page} {link_props} /> Features shown: Simple string routes with auto-generated labels
Custom Labels
<script lang="ts">
import { Nav } from '$lib'
import { page } from '$app/state'
const link_props = { onclick: (event: MouseEvent) => event.preventDefault() }
</script>
<Nav
routes={['/ui', '/css-classes', '/kit-form-actions']}
labels={{
'/ui': 'UI Components',
'/css-classes': 'CSS Classes',
'/kit-form-actions': 'Form Actions',
}}
{page}
{link_props}
/> Features shown: Override auto-generated labels via labels prop
Dropdown Menus
Use tuple syntax [parent, [children...]] for nested routes. When the parent exists in children array, it becomes a clickable link. Otherwise, itβs just a label.
<script lang="ts">
import { Nav } from '$lib'
import { page } from '$app/state'
const routes = [
'/',
['/docs', ['/docs', '/docs/api', '/docs/guides']], // /docs is clickable (in children)
['/help', ['/help/faq', '/help/support']], // /help is just a label (not in children)
'/about',
]
const link_props = { onclick: (event: MouseEvent) => event.preventDefault() }
</script>
<Nav {routes} {page} {link_props} /> Features shown:
- Dropdown with clickable parent (
/docsappears in children array) - Dropdown with non-clickable parent (
/helpnot in children array) - Mixed route formats (strings and tuples)
Keyboard Navigation
- Tab: Navigate between items
- Enter/Space: Open dropdown or follow link
- Arrow Down/Up: Navigate dropdown items
- Escape: Close menus
Object Route Format
For full control, use objects with all available properties:
<script lang="ts">
import { Nav } from '$lib'
import { page } from '$app/state'
const routes = [
{ href: '/', label: 'Home' },
{ href: '/docs', children: ['/docs/api', '/docs/guides'] },
{ href: '/pricing' },
{ separator: true }, // standalone separator
{ href: '/admin', disabled: 'Login required' },
{ href: '/beta', disabled: true, separator: true }, // separator after disabled item
{ href: '/settings', align: 'right', tooltip: 'Configure your preferences' },
{ href: 'https://github.com', label: 'GitHub', external: true, align: 'right' },
]
const link_props = { onclick: (event: MouseEvent) => event.preventDefault() }
</script>
<Nav {routes} {page} {link_props} /> Features shown:
- Custom labels:
label: 'Home'overrides auto-generated label - Dropdowns:
childrenarray creates nested menu - Standalone separator:
{ separator: true }with no href - Disabled with tooltip:
disabled: 'Login required'shows message on hover - Disabled boolean:
disabled: truejust grays out - Separator after item:
separator: trueon a route object - Tooltip:
tooltip: 'text'shows on-hover tooltip - Right alignment:
align: 'right'pushes items to the right - External links:
external: trueaddstarget="_blank" rel="noopener noreferrer"
Route Object Properties
| Property | Type | Description |
|---|---|---|
href | string | Required (except separator-only). The URL |
label | string | Custom label (default: derived from href) |
children | string[] | Sub-routes for dropdown menu |
disabled | boolean \| string | Disable item; string shows as tooltip |
separator | boolean | Render visual divider after this item |
align | 'left' \| 'right' | Item alignment (default: left) |
external | boolean | Opens in new tab with rel="noopener noreferrer" for security |
tooltip | string | On-hover tooltip text |
class | string | Custom CSS class |
style | string | Custom inline styles |
Snippets
Custom Link Rendering
Use the link snippet to customize how all links render:
<script lang="ts">
import { Nav } from '$lib'
import { page } from '$app/state'
const routes = ['/', '/about', '/contact']
</script>
<Nav {routes} {page}>
{#snippet link({ href, label })}
<a {href} onclick={(event: MouseEvent) => event.preventDefault()}>π {label}</a>
{/snippet}
</Nav> Custom Children
Add extra content to the nav menu via children snippet:
<script lang="ts">
import { Nav } from '$lib'
import { page } from '$app/state'
const routes = ['/', '/about', '/blog']
const link_props = { onclick: (event: MouseEvent) => event.preventDefault() }
</script>
<Nav {routes} {page} {link_props}>
{#snippet children({ is_open })}
<button
style="padding: 4pt 12pt; background: var(--sms-selected-bg, mediumseagreen); border: none; border-radius: 6px; color: white; cursor: pointer"
onclick={() => alert('Custom action!')}
>
β‘ Action
</button>
{#if is_open}
<span style="opacity: 0.6; font-size: 0.85em">(menu open)</span>
{/if}
{/snippet}
</Nav> Features shown:
- Custom button in nav
- Access to
is_openstate (visible when burger menu is open on mobile)
Item Snippet with Custom Properties
Use item snippet for per-item customization. The render_default escape hatch renders the default link:
<script lang="ts">
import { Nav } from '$lib'
import { page } from '$app/state'
const routes = [
{ href: '/', label: 'Home', icon: 'π ' },
{ href: '/docs', label: 'Docs', icon: 'π' },
{ href: '/settings', label: 'Settings', icon: 'βοΈ', align: 'right' },
]
const link_props = { onclick: (event: MouseEvent) => event.preventDefault() }
</script>
<Nav {routes} {page} {link_props}>
{#snippet item({ route, render_default })}
<span style="display: flex; align-items: center; gap: 0.3em">
{#if route.icon}
<span>{route.icon}</span>
{/if}
{@render render_default()}
</span>
{/snippet}
</Nav> Features shown:
- Custom
iconproperty on route objects (any extra props are allowed) render_default()renders the standard link/span- Right-aligned item with icon
Callbacks
Handle navigation events with onnavigate, onopen, and onclose:
<script lang="ts">
import { Nav } from '$lib'
import { page } from '$app/state'
const routes = ['/', '/about', '/contact', '/blog']
const link_props = { onclick: (event: MouseEvent) => event.preventDefault() }
let nav_message = $state('')
let menu_status = $state('closed')
</script>
<div style="margin-bottom: 1em">
<strong>Last action:</strong>
{nav_message || 'None'} | <strong>Menu:</strong>
{menu_status}
</div>
<Nav
{routes}
{page}
{link_props}
onnavigate={({ href }) => {
nav_message = `Navigated to ${href}`
return false // returning false prevents navigation
}}
onopen={() => (menu_status = 'open')}
onclose={() => (menu_status = 'closed')}
/> Features shown:
onnavigatecallback with{ href, event, route }- returnfalseto prevent navigationonopen/onclosecallbacks fire when burger menu toggles (resize window to test)
Custom Breakpoint
Control when mobile burger menu appears with the breakpoint prop (default: 767):
<!-- Always show mobile menu -->
<Nav {routes} breakpoint={9999} />
<!-- Never show mobile menu -->
<Nav {routes} breakpoint={0} />
<!-- Custom breakpoint -->
<Nav {routes} breakpoint={1024} /> Styling
Customize via CSS variables:
nav {
--nav-border-radius: 6pt;
--nav-link-bg-hover: rgba(255, 255, 255, 0.1);
--nav-link-active-color: mediumseagreen;
--nav-dropdown-bg: var(--surface-bg);
--nav-dropdown-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
--nav-dropdown-z-index: 100;
--nav-mobile-z-index: 2;
--nav-disabled-opacity: 0.5;
--nav-separator-color: currentColor;
--nav-separator-margin: 0 0.25em;
}