Navigation Menu
Menus & Navigationimport { NavigationMenu } from "bits-ui" Rich navigation
Two flyouts share a measured Viewport, with a moving Indicator and a dense link grid
<script lang="ts">
import { NavigationMenu } from 'bits-ui';
import { ChevronDown, Layers3 } from '@lucide/svelte';
const products = [
{ title: 'Deployments', href: '/components/progress', detail: 'Promote builds and inspect rollout health.' },
{ title: 'Feature flags', href: '/components/switch', detail: 'Control releases without shipping again.' },
{ title: 'Environments', href: '/components/tabs', detail: 'Separate production, preview, and local state.' },
{ title: 'Interaction hints', href: '/components/tooltip', detail: 'Add concise help without crowding the interface.' }
];
const resources = [
{ title: 'Component index', href: '/components', detail: 'Browse every primitive in this showcase.' },
{ title: 'Builder', href: '/builder', detail: 'Compose an interface from reusable blocks.' },
{ title: 'Accessibility', href: '/components/accordion', detail: 'Learn the interaction patterns behind the UI.' }
];
const triggerClass =
'group flex items-center gap-1 rounded-md px-3 py-2 text-sm font-medium outline-none data-highlighted:bg-ink-800 data-[state=open]:bg-ink-800';
const linkClass =
'block rounded-md p-3 outline-none data-highlighted:bg-ink-800 data-highlighted:text-ink-50 focus:bg-ink-800';
</script>
<NavigationMenu.Root class="relative z-10 mx-auto flex w-full max-w-2xl justify-center">
<NavigationMenu.List class="card relative flex list-none items-center gap-1 p-1">
<NavigationMenu.Item value="platform">
<NavigationMenu.Trigger class={triggerClass}>
Platform
<ChevronDown class="size-3.5 transition-transform group-data-[state=open]:rotate-180" />
</NavigationMenu.Trigger>
<NavigationMenu.Content class="panel animate-pop-in w-[34rem] p-3">
<div class="grid grid-cols-[0.9fr_1.6fr] gap-3">
<NavigationMenu.Link href="/" class="flex min-h-52 flex-col justify-end rounded-lg border border-brand-500/30 bg-brand-500/10 p-5 outline-none focus:ring-2 focus:ring-brand-500">
<Layers3 class="mb-auto size-7 text-brand-400" />
<p class="font-semibold text-ink-50">Bits workspace</p>
<p class="muted mt-1 text-xs leading-relaxed">Headless interaction primitives with a production-ready visual skin.</p>
</NavigationMenu.Link>
<ul class="grid grid-cols-2 gap-1">
{#each products as product (product.title)}
<li>
<NavigationMenu.Link href={product.href} class={linkClass}>
<p class="text-sm font-medium">{product.title}</p>
<p class="muted mt-1 text-xs leading-snug">{product.detail}</p>
</NavigationMenu.Link>
</li>
{/each}
</ul>
</div>
</NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Item value="resources">
<NavigationMenu.Trigger class={triggerClass}>
Resources
<ChevronDown class="size-3.5 transition-transform group-data-[state=open]:rotate-180" />
</NavigationMenu.Trigger>
<NavigationMenu.Content class="panel animate-pop-in w-[28rem] p-3">
<ul class="grid gap-1">
{#each resources as resource (resource.title)}
<li>
<NavigationMenu.Link href={resource.href} class={linkClass}>
<p class="text-sm font-medium">{resource.title}</p>
<p class="muted mt-1 text-xs">{resource.detail}</p>
</NavigationMenu.Link>
</li>
{/each}
</ul>
</NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Item>
<NavigationMenu.Link href="/components" class={triggerClass}>All components</NavigationMenu.Link>
</NavigationMenu.Item>
<NavigationMenu.Indicator class="top-full z-20 flex h-2.5 items-end justify-center data-[state=hidden]:animate-fade-out data-[state=visible]:animate-fade-in">
<span class="size-2 rotate-45 border-l border-t border-ink-700 bg-ink-900"></span>
</NavigationMenu.Indicator>
</NavigationMenu.List>
<div class="absolute left-1/2 top-full flex -translate-x-1/2 justify-center pt-2">
<NavigationMenu.Viewport class="animate-pop-in h-[var(--bits-navigation-menu-viewport-height)] w-[var(--bits-navigation-menu-viewport-width)] overflow-hidden rounded-xl transition-[width,height] duration-200" />
</div>
</NavigationMenu.Root>
Click to open
openOnHover=false makes the flyout deliberate and keeps it open until outside interaction or Escape
<script lang="ts">
import { NavigationMenu } from 'bits-ui';
import { ChevronDown } from '@lucide/svelte';
const triggerClass =
'group flex items-center gap-1 rounded-md px-3 py-2 text-sm font-medium outline-none data-highlighted:bg-ink-800 data-[state=open]:bg-ink-800';
</script>
<div class="grid min-h-56 place-items-start justify-center pt-6">
<NavigationMenu.Root class="relative flex justify-center">
<NavigationMenu.List class="card relative flex list-none items-center gap-1 p-1">
<NavigationMenu.Item value="company" openOnHover={false}>
<NavigationMenu.Trigger class={triggerClass}>
Company
<ChevronDown class="size-3.5 transition-transform group-data-[state=open]:rotate-180" />
</NavigationMenu.Trigger>
<NavigationMenu.Content class="panel animate-pop-in w-72 p-3">
<p class="text-sm font-semibold">Built for deliberate navigation</p>
<p class="muted mt-1 text-xs leading-relaxed">This item opts out of hover opening. Click the trigger, then move through the links with Tab.</p>
<div class="mt-3 grid gap-1">
<NavigationMenu.Link href="/components" class="rounded-md px-3 py-2 text-sm outline-none focus:bg-ink-800">Component library</NavigationMenu.Link>
<NavigationMenu.Link href="/builder" class="rounded-md px-3 py-2 text-sm outline-none focus:bg-ink-800">Interface builder</NavigationMenu.Link>
</div>
</NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Item>
<NavigationMenu.Link href="/components/navigation-menu" class={triggerClass}>Documentation</NavigationMenu.Link>
</NavigationMenu.Item>
<NavigationMenu.Indicator class="top-full z-20 flex h-2.5 items-end justify-center data-[state=hidden]:animate-fade-out data-[state=visible]:animate-fade-in">
<span class="size-2 rotate-45 border-l border-t border-ink-700 bg-ink-900"></span>
</NavigationMenu.Indicator>
</NavigationMenu.List>
<div class="absolute left-1/2 top-full flex -translate-x-1/2 justify-center pt-2">
<NavigationMenu.Viewport class="animate-pop-in h-[var(--bits-navigation-menu-viewport-height)] w-[var(--bits-navigation-menu-viewport-width)] overflow-hidden rounded-xl transition-[width,height] duration-200" />
</div>
</NavigationMenu.Root>
</div>
Viewport relocates active Content and manages Tab focus across its links automatically. Trigger activation and directional keys move through the navigation structure; Escape closes an active flyout. Unlike command menus, NavigationMenu links keep normal link semantics and do not expose an item textValue typeahead API.
Root defaults to a 200 ms hover delay and a 300 ms skip-delay window. Viewport publishes --bits-navigation-menu-viewport-width and --bits-navigation-menu-viewport-height; Content adds directional data-motion values for coordinated size and slide transitions. Setting Item's openOnHover false also changes dismissal: pointer exit no longer closes it.