SvNavPane

An Outlook-style navigation pane: collapsible sections, items with icons and badge counts, one level of nested sub-items, and an optional bottom module strip.

SvNavPane is the app-shell sidebar. Sections group items and can collapse; items carry an icon, a badge (unread counts), and optionally one level of children. It supports a single-select highlight, arrow-key navigation, an icon-only collapsed rail, and RTL. The bottom modules strip reproduces Outlook's Mail / Calendar / People switch, complete with a drag splitter that moves modules between full rows and an icon rail. Colors come from the --sg-* tokens.

Related: SvTree · SvBreadcrumb · Navigation & rich overview

Installation

Add it with the CLI - this drops a ready-to-edit SvNavPane starter into your app:

Prefer to see it first? npx @svgrid/ui try nav-pane opens it in a throwaway sandbox - no project needed.

Or install the package and import it directly. SvNavPane ships free in @svgrid/grid (dependency-free):

The examples on this page import from @svgrid/grid:

<script lang="ts">
  import { SvButton, SvNavPane } from '@svgrid/grid'
</script>
import { SvNavPane } from '@svgrid/grid'

Example

Open the live example: Navigation pane (Layout)

<script lang="ts">
  import { SvNavPane, type NavSection } from '@svgrid/grid'
  let value = $state<string | null>('inbox')
  const sections: NavSection[] = [
    { id: 'mail', label: 'Mail', items: [
      { id: 'inbox', label: 'Inbox', badge: 12 },
      { id: 'sent', label: 'Sent' },
    ] },
  ]
</script>

<SvNavPane {sections} bind:value onSelect={(id) => load(id)} />

Props

Prop Type Default Description
sections ReadonlyArray<NavSection> - The grouped navigation items.
value string | null null Selected item id (bindable).
onSelect (id: string) => void - Fires when a leaf item is chosen.
expandedSections string[] all open Open section ids (bindable). Default opens every section.
expandedItems string[] [] Open nested-item ids (bindable).
collapsed boolean false Icon-only rail; labels and badges hidden.
ariaLabel string Navigation Accessible name for the <nav> landmark.
dir EditorDir - Text direction; rtl mirrors the pane.
modules ReadonlyArray<NavModule> - Outlook-style bottom module buttons.
moduleValue string | null null Selected module id (bindable).
onModuleSelect (id: string) => void - Fires when a module is chosen.
moduleRows number all How many modules show as full rows; the rest collapse to a rail (bindable).
height number - Pane height in px; lets the module splitter redistribute space.

Exported helper types:

Examples

Icons and badge counts

Pass a Snippet per item for an icon, and a badge for a count:

{#snippet inboxIcon()}<MailIcon />{/snippet}
const items = [{ id: 'inbox', label: 'Inbox', icon: inboxIcon, badge: 12 }]

Collapsible icon rail

Bind collapsed to a toggle for a slim, icon-only sidebar. Item labels move into title tooltips automatically:

<SvNavPane {sections} bind:value collapsed={railOpen} />

The Outlook module strip

Give modules and a fixed height, then the drag splitter lets the user pull modules between labelled rows and the compact icon rail (bindable moduleRows):

<SvNavPane {sections} bind:value {modules} bind:moduleValue height={520} />

A workspace sidebar wired to routing

A full app-shell pane: labelled sections, an icon and unread badge, one level of nested folders, a disabled item, and a collapse toggle. Route on onSelect and bind value so the highlight follows the active page:

<script lang="ts">
  import { SvNavPane, SvButton, type NavSection } from '@svgrid/grid'
  import { goto } from '$app/navigation'

  let value = $state<string | null>('inbox')
  let rail = $state(false)

  const sections: NavSection[] = [
    { id: 'mail', label: 'Mail', items: [
      { id: 'inbox', label: 'Inbox', icon: inboxIcon, badge: 12 },
      { id: 'drafts', label: 'Drafts', badge: 3 },
      { id: 'folders', label: 'Folders', children: [
        { id: 'work', label: 'Work' },
        { id: 'personal', label: 'Personal' },
      ] },
    ] },
    { id: 'settings', label: 'Settings', collapsible: true, items: [
      { id: 'account', label: 'Account' },
      { id: 'billing', label: 'Billing', disabled: true },
    ] },
  ]
</script>

{#snippet inboxIcon()}<span>📥</span>{/snippet}

<SvButton size="sm" onclick={() => (rail = !rail)}>{rail ? 'Expand' : 'Collapse'}</SvButton>
<SvNavPane {sections} bind:value collapsed={rail} onSelect={(id) => goto(`/mail/${id}`)} />

Tip: an item that has children toggles its sub-list on click instead of selecting, so a parent item never becomes the highlighted value and never fires onSelect. Give routable destinations to the leaf items (work, personal), not to the folder that contains them.

Accessibility

More examples

Mail client

An Outlook-shaped mail client in one file. The inbox is a SvGrid with sender avatars, conversation counts, quick filters, hover actions and bulk actions on ticked rows; the reading pane shows the whole conversation with quoted text folded; the SvRichText composer has recipient chips with contact suggestions, Cc, attachments, a signature and a five-second Undo send. New mail arrives while the page is open, Gmail-style keys work (press ?), an accepted invite lands on the Calendar module (the same grid as a scheduler), and Export writes a folder to Excel, CSV or PDF.

Open the live example: Mail client (App Templates)

See also

Live examples

  • Navigation pane - SvNavPane: an Outlook-style sidebar - collapsible sections, unread badges, nested folders, single-select highlight, arrow-key nav, an icon-only collapsed rail, and a bottom module strip with a drag splitter (drag up to collapse modules into an icon rail).
  • Mail client - An Outlook-shaped mail client in one file. The inbox is a SvGrid with sender avatars, conversation counts, quick filters, hover actions and bulk actions on ticked rows; the reading pane shows the whole conversation with quoted text folded; the SvRichText composer has recipient chips with contact suggestions, Cc, attachments, a signature and a five-second Undo send. New mail arrives while the page is open, Gmail-style keys work (press ?), an accepted invite lands on the Calendar module (the same grid as a scheduler), and Export writes a folder to Excel, CSV or PDF.