Navigation Menu

A site header menu. Triggers open panels of links, and one viewport resizes and slides between them as the pointer moves across the row.

<script lang="ts">
    import * as NavigationMenu from '@sivir-ui/svelte/components/navigation-menu';

    const products = [
        {
            title: 'Analytics',
            description: 'Dashboards and funnels for every release.',
            href: '#analytics'
        },
        {
            title: 'Feature flags',
            description: 'Ship behind a flag and roll out by segment.',
            href: '#flags'
        },
        {
            title: 'Session replay',
            description: 'Watch the sessions behind a bug report.',
            href: '#replay'
        },
        {
            title: 'Experiments',
            description: 'Test two versions and keep the winner.',
            href: '#experiments'
        }
    ];

    const resources = [
        {
            title: 'Documentation',
            description: 'Guides and API reference.',
            href: '#docs'
        },
        {
            title: 'Changelog',
            description: 'What shipped this week.',
            href: '#changelog'
        },
        {
            title: 'Community',
            description: 'Questions and answers from other teams.',
            href: '#community'
        }
    ];
</script>

<div class="flex w-full justify-center">
    <NavigationMenu.Root aria-label="Main">
        <NavigationMenu.List>
            <NavigationMenu.Item value="products">
                <NavigationMenu.Trigger>Products</NavigationMenu.Trigger>
                <NavigationMenu.Content class="grid w-120 gap-0.5 sm:grid-cols-2">
                    {#each products as product (product.href)}
                        <NavigationMenu.Link href={product.href}>
                            <NavigationMenu.LinkTitle>{product.title}</NavigationMenu.LinkTitle>
                            <NavigationMenu.LinkDescription>
                                {product.description}
                            </NavigationMenu.LinkDescription>
                        </NavigationMenu.Link>
                    {/each}
                </NavigationMenu.Content>
            </NavigationMenu.Item>
            <NavigationMenu.Item value="resources">
                <NavigationMenu.Trigger>Resources</NavigationMenu.Trigger>
                <NavigationMenu.Content class="w-72">
                    {#each resources as resource (resource.href)}
                        <NavigationMenu.Link href={resource.href}>
                            <NavigationMenu.LinkTitle>{resource.title}</NavigationMenu.LinkTitle>
                            <NavigationMenu.LinkDescription>
                                {resource.description}
                            </NavigationMenu.LinkDescription>
                        </NavigationMenu.Link>
                    {/each}
                </NavigationMenu.Content>
            </NavigationMenu.Item>
            <NavigationMenu.Item>
                <NavigationMenu.Link href="#pricing">Pricing</NavigationMenu.Link>
            </NavigationMenu.Item>
        </NavigationMenu.List>
        <NavigationMenu.Viewport />
    </NavigationMenu.Root>
</div>

Installation

Copy the command below and run it in your terminal.

bunx @sivir-ui/svelte add navigation-menu

Usage

Each NavigationMenu.Item pairs a NavigationMenu.Trigger with a NavigationMenu.Content, or holds a single top-level NavigationMenu.Link. Content does not render where you write it. The open item's content renders inside NavigationMenu.Viewport, so place one Viewport after the list. The Viewport renders in a layer above the page, under the menu, so the surrounding layout needs no room for it and no parent clips it. Size each panel with a width class on its Content, and the Viewport animates between those sizes.

Hovering a trigger opens its panel after openDelay (150ms). Once a panel is open, moving to another trigger switches at once. Leaving the menu closes it after closeDelay (200ms). Clicking a trigger pins its panel open until you click it again, click outside, or press Escape.

import * as NavigationMenu from '@sivir-ui/svelte/components/navigation-menu';

<NavigationMenu.Root aria-label="Main">
  <NavigationMenu.List>
    <NavigationMenu.Item value="products">
      <NavigationMenu.Trigger>Products</NavigationMenu.Trigger>
      <NavigationMenu.Content class="grid w-120 grid-cols-2">
        <NavigationMenu.Link href="/analytics">
          <NavigationMenu.LinkTitle>Analytics</NavigationMenu.LinkTitle>
          <NavigationMenu.LinkDescription>
            Dashboards and funnels for every release.
          </NavigationMenu.LinkDescription>
        </NavigationMenu.Link>
      </NavigationMenu.Content>
    </NavigationMenu.Item>
    <NavigationMenu.Item>
      <NavigationMenu.Link href="/pricing">Pricing</NavigationMenu.Link>
    </NavigationMenu.Item>
  </NavigationMenu.List>
  <NavigationMenu.Viewport />
</NavigationMenu.Root>

Examples

Simple links

<script lang="ts">
    import * as NavigationMenu from '@sivir-ui/svelte/components/navigation-menu';
</script>

<div class="flex w-full justify-center">
    <NavigationMenu.Root aria-label="Docs">
        <NavigationMenu.List>
            <NavigationMenu.Item value="guides">
                <NavigationMenu.Trigger>Guides</NavigationMenu.Trigger>
                <NavigationMenu.Content class="w-48">
                    <NavigationMenu.Link href="#install">Installation</NavigationMenu.Link>
                    <NavigationMenu.Link href="#theming">Theming</NavigationMenu.Link>
                    <NavigationMenu.Link href="#forms">Forms</NavigationMenu.Link>
                </NavigationMenu.Content>
            </NavigationMenu.Item>
            <NavigationMenu.Item value="reference">
                <NavigationMenu.Trigger>Reference</NavigationMenu.Trigger>
                <NavigationMenu.Content class="w-56">
                    <NavigationMenu.Link href="#components">Components</NavigationMenu.Link>
                    <NavigationMenu.Link href="#tokens">Design tokens</NavigationMenu.Link>
                    <NavigationMenu.Link href="#cli">Command line</NavigationMenu.Link>
                    <NavigationMenu.Link href="#utilities">Utilities</NavigationMenu.Link>
                </NavigationMenu.Content>
            </NavigationMenu.Item>
            <NavigationMenu.Item>
                <NavigationMenu.Link href="#blog">Blog</NavigationMenu.Link>
            </NavigationMenu.Item>
        </NavigationMenu.List>
        <NavigationMenu.Viewport />
    </NavigationMenu.Root>
</div>

A link with plain text renders as a single row. Hover and keyboard focus share one highlight that travels between links, the same one menus use. Arrow keys move between links in a panel, and between items in the top row.

Controlled

Open item: none
<script lang="ts">
    import * as NavigationMenu from '@sivir-ui/svelte/components/navigation-menu';
    import * as Typography from '@sivir-ui/svelte/components/typography';

    let value = $state('');
</script>

<div class="flex w-full flex-col items-center gap-3">
    <Typography.Metadata class="tabular-nums">
        Open item: {value === '' ? 'none' : value}
    </Typography.Metadata>
    <NavigationMenu.Root bind:value aria-label="Company">
        <NavigationMenu.List>
            <NavigationMenu.Item value="company">
                <NavigationMenu.Trigger>Company</NavigationMenu.Trigger>
                <NavigationMenu.Content class="w-48">
                    <NavigationMenu.Link href="#about">About</NavigationMenu.Link>
                    <NavigationMenu.Link href="#careers">Careers</NavigationMenu.Link>
                    <NavigationMenu.Link href="#press">Press</NavigationMenu.Link>
                </NavigationMenu.Content>
            </NavigationMenu.Item>
            <NavigationMenu.Item value="legal">
                <NavigationMenu.Trigger>Legal</NavigationMenu.Trigger>
                <NavigationMenu.Content class="w-48">
                    <NavigationMenu.Link href="#terms">Terms</NavigationMenu.Link>
                    <NavigationMenu.Link href="#privacy">Privacy</NavigationMenu.Link>
                </NavigationMenu.Content>
            </NavigationMenu.Item>
        </NavigationMenu.List>
        <NavigationMenu.Viewport />
    </NavigationMenu.Root>
</div>

Bind value to read or set the open item. It holds the open item's value, or an empty string when the menu is closed.

Small screens

Acme
<script lang="ts">
    import * as FullscreenNav from '@sivir-ui/svelte/components/fullscreen-nav';
    import * as NavigationMenu from '@sivir-ui/svelte/components/navigation-menu';
</script>

<div class="flex w-full flex-col">
    <header class="flex items-center justify-between gap-4 border-b border-border pb-3">
        <span class="[font-weight:var(--font-weight-header)]">Acme</span>
        <NavigationMenu.Root aria-label="Main" class="hidden md:block">
            <NavigationMenu.List>
                <NavigationMenu.Item value="product">
                    <NavigationMenu.Trigger>Product</NavigationMenu.Trigger>
                    <NavigationMenu.Content class="w-48">
                        <NavigationMenu.Link href="#features">Features</NavigationMenu.Link>
                        <NavigationMenu.Link href="#security">Security</NavigationMenu.Link>
                    </NavigationMenu.Content>
                </NavigationMenu.Item>
                <NavigationMenu.Item>
                    <NavigationMenu.Link href="#pricing">Pricing</NavigationMenu.Link>
                </NavigationMenu.Item>
            </NavigationMenu.List>
            <NavigationMenu.Viewport />
        </NavigationMenu.Root>
        <div class="md:hidden">
            <FullscreenNav.Root>
                <FullscreenNav.Trigger aria-label="Open navigation" />
                <FullscreenNav.Content label="Main">
                    <header class="flex items-center justify-between py-1">
                        <span class="[font-weight:var(--font-weight-header)]">Acme</span>
                        <FullscreenNav.Close />
                    </header>
                    <div class="mt-12 flex flex-col gap-10">
                        <FullscreenNav.Group heading="Product">
                            <FullscreenNav.Link href="#features">Features</FullscreenNav.Link>
                            <FullscreenNav.Link href="#security">Security</FullscreenNav.Link>
                        </FullscreenNav.Group>
                        <FullscreenNav.Link href="#pricing">Pricing</FullscreenNav.Link>
                    </div>
                </FullscreenNav.Content>
            </FullscreenNav.Root>
        </div>
    </header>
</div>

Hover panels do not suit narrow touch screens. Hide the menu below md and offer the same links in a FullscreenNav instead.

Anatomy

NavigationMenu.Root - Owns the open item and hover intent.

NavigationMenu.List - Lays out the top-level items in a row.

NavigationMenu.Item - Pairs a trigger with its panel.

NavigationMenu.Trigger - Opens the item panel on hover or click.

NavigationMenu.Content - Declares the panel shown in the viewport.

NavigationMenu.Viewport - Renders the open panel and morphs between panel sizes.

NavigationMenu.Link - Renders a top-level or panel link.

NavigationMenu.LinkTitle - Renders the title of a rich link.

NavigationMenu.LinkDescription - Renders the supporting line of a rich link.