svelte-multiselect Svelte MultiSelect

Nav Component

Flexible, accessible navigation with dropdown support, mobile burger menu, and keyboard navigation.

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

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:

Keyboard Navigation

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:

Route Object Properties

PropertyTypeDescription
hrefstringRequired (except separator-only). The URL
labelstringCustom label (default: derived from href)
childrenstring[]Sub-routes for dropdown menu
disabledboolean \| stringDisable item; string shows as tooltip
separatorbooleanRender visual divider after this item
align'left' \| 'right'Item alignment (default: left)
externalbooleanOpens in new tab with rel="noopener noreferrer" for security
tooltipstringOn-hover tooltip text
classstringCustom CSS class
stylestringCustom inline styles

Snippets

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:

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:

Callbacks

Handle navigation events with onnavigate, onopen, and onclose:

Last action: None | Menu: closed
<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:

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;
}