Beta, expect API change, and bugs. Hit one? Tell us.

Navigation Menu

A navigation menu with floating panels.

<script lang="ts">
  import { NavigationMenu } from '@shardsui/svelte/navigation-menu'

  type LinkItem = { href: string; title: string; description: string }

  const topics: LinkItem[] = [
    {
      href: '#',
      title: 'Frontend',
      description: 'From first components to full working apps.'
    },
    { href: '#', title: 'Design', description: 'Type scale, color tokens, and layout grids.' },
    {
      href: '#',
      title: 'Accessibility',
      description: 'Semantic HTML, ARIA, and keyboard nav.'
    },
    { href: '#', title: 'Motion', description: 'Easing, timing, and interface animation.' }
  ]

  const resources: LinkItem[] = [
    {
      href: '#',
      title: 'Docs',
      description: 'Guides and reference for every feature.'
    },
    { href: '#', title: 'Changelog', description: 'What shipped and when.' },
    { href: '#', title: 'Blog', description: 'Updates, tips, and release notes.' }
  ]
</script>

<NavigationMenu.Root class="min-w-max rounded-lg bg-gray-50 p-1 text-gray-900">
  <NavigationMenu.List class="relative flex gap-px">
    <NavigationMenu.Item>
      <NavigationMenu.Trigger
        class="flex h-8 items-center justify-center gap-1.5 rounded-md bg-gray-50 px-2 text-sm font-normal text-gray-900 no-underline select-none hover:bg-gray-100 focus-visible:relative focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-gray-950 active:bg-gray-100 data-popup-open:bg-gray-100 min-[501px]:px-3"
      >
        Products
        <NavigationMenu.Icon
          class="transition-transform duration-200 ease-[ease] data-popup-open:rotate-180"
        >
          {@render chevronDownIcon()}
        </NavigationMenu.Icon>
      </NavigationMenu.Trigger>

      <NavigationMenu.Content
        class="h-full w-[calc(100vw-40px)] p-4 transition-[opacity,translate] duration-[calc(var(--duration)*0.5),var(--duration)] ease-[ease,var(--easing)] data-ending-style:opacity-0 data-starting-style:opacity-0 data-ending-style:data-[activation-direction=left]:translate-x-1/2 data-starting-style:data-[activation-direction=left]:-translate-x-1/2 data-ending-style:data-[activation-direction=right]:-translate-x-1/2 data-starting-style:data-[activation-direction=right]:translate-x-1/2 min-[500px]:w-max min-[500px]:min-w-100"
      >
        <ul class="grid list-none grid-cols-1 min-[500px]:grid-cols-[13rem_13rem]">
          {#each topics as item (item.title)}
            {@render card(item)}
          {/each}
        </ul>
      </NavigationMenu.Content>
    </NavigationMenu.Item>

    <NavigationMenu.Item>
      <NavigationMenu.Trigger
        class="flex h-8 items-center justify-center gap-1.5 rounded-md bg-gray-50 px-2 text-sm font-normal text-gray-900 no-underline select-none hover:bg-gray-100 focus-visible:relative focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-gray-950 active:bg-gray-100 data-popup-open:bg-gray-100 min-[501px]:px-3"
      >
        Resources
        <NavigationMenu.Icon
          class="transition-transform duration-200 ease-[ease] data-popup-open:rotate-180"
        >
          {@render chevronDownIcon()}
        </NavigationMenu.Icon>
      </NavigationMenu.Trigger>

      <NavigationMenu.Content
        class="h-full w-[calc(100vw-40px)] p-4 transition-[opacity,translate] duration-[calc(var(--duration)*0.5),var(--duration)] ease-[ease,var(--easing)] data-ending-style:opacity-0 data-starting-style:opacity-0 data-ending-style:data-[activation-direction=left]:translate-x-1/2 data-starting-style:data-[activation-direction=left]:-translate-x-1/2 data-ending-style:data-[activation-direction=right]:-translate-x-1/2 data-starting-style:data-[activation-direction=right]:translate-x-1/2 min-[500px]:w-max min-[500px]:min-w-100"
      >
        <ul class="flex max-w-100 list-none flex-col justify-center">
          {#each resources as item (item.title)}
            {@render card(item)}
          {/each}
        </ul>
      </NavigationMenu.Content>
    </NavigationMenu.Item>

    <NavigationMenu.Item>
      <NavigationMenu.Link
        class="flex h-8 items-center justify-center gap-1.5 rounded-md bg-gray-50 px-2 text-sm font-normal text-gray-900 no-underline select-none hover:bg-gray-100 focus-visible:relative focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-gray-950 active:bg-gray-100 min-[501px]:px-3"
        href="#">Pricing</NavigationMenu.Link
      >
    </NavigationMenu.Item>
  </NavigationMenu.List>

  <NavigationMenu.Portal>
    <NavigationMenu.Positioner
      sideOffset={10}
      collisionPadding={{ top: 5, bottom: 5, left: 20, right: 20 }}
      collisionAvoidance={{ side: 'none' }}
      style="--duration:0.35s;--easing:cubic-bezier(0.22, 1, 0.36, 1);"
      class="box-border h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom] duration-(--duration) ease-(--easing) before:absolute before:content-[''] data-instant:transition-none data-[side=bottom]:before:inset-x-0 data-[side=bottom]:before:-top-2.5 data-[side=bottom]:before:h-2.5 data-[side=left]:before:inset-y-0 data-[side=left]:before:-right-2.5 data-[side=left]:before:w-2.5 data-[side=right]:before:inset-y-0 data-[side=right]:before:-left-2.5 data-[side=right]:before:w-2.5 data-[side=top]:before:inset-x-0 data-[side=top]:before:-bottom-2.5 data-[side=top]:before:h-2.5"
    >
      <NavigationMenu.Popup
        class="relative h-(--popup-height) w-(--popup-width) origin-(--transform-origin) rounded-lg bg-gray-50 text-gray-900 shadow-lg outline-1 outline-gray-200 transition-[opacity,transform,width,height,scale] duration-(--duration) ease-(--easing) data-ending-style:scale-90 data-ending-style:opacity-0 data-ending-style:duration-150 data-ending-style:ease-[ease] data-starting-style:scale-90 data-starting-style:opacity-0"
      >
        <NavigationMenu.Arrow
          class="flex transition-[left,right] duration-(--duration) ease-(--easing) data-[side=bottom]:-top-2 data-[side=left]:-right-3.25 data-[side=left]:rotate-90 data-[side=right]:-left-3.25 data-[side=right]:-rotate-90 data-[side=top]:-bottom-2 data-[side=top]:rotate-180"
        >
          {@render arrow()}
        </NavigationMenu.Arrow>
        <NavigationMenu.Viewport class="relative size-full overflow-hidden rounded-[inherit]" />
      </NavigationMenu.Popup>
    </NavigationMenu.Positioner>
  </NavigationMenu.Portal>
</NavigationMenu.Root>

{#snippet chevronDownIcon()}
  <svg class="size-4" viewBox="0 0 24 24" fill="none" aria-hidden="true">
    <path
      d="M5.75 9.5L12 15.75L18.25 9.5"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
  </svg>
{/snippet}

{#snippet arrow()}
  <svg width="20" height="10" viewBox="0 0 20 10" fill="none">
    <path
      d="M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z"
      class="fill-gray-50"
    />
    <path
      d="M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z"
      class="fill-gray-200"
    />
    <path
      d="M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z"
    />
  </svg>
{/snippet}

{#snippet card(item: LinkItem)}
  <li>
    <NavigationMenu.Link
      href={item.href}
      class="relative block size-full rounded-md p-2 text-left text-inherit no-underline hover:bg-gray-100 focus-visible:relative focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-gray-950 min-[501px]:p-3"
    >
      <h3 class="m-0 mb-1 text-sm leading-4 font-normal">{item.title}</h3>
      <p class="m-0 text-sm/5 text-gray-500">{item.description}</p>
    </NavigationMenu.Link>
  </li>
{/snippet}

Anatomy

<script>
  import { NavigationMenu } from '@shardsui/svelte/navigation-menu'
</script>

<NavigationMenu.Root>
  <NavigationMenu.List>
    <NavigationMenu.Item>
      <NavigationMenu.Trigger>
        <NavigationMenu.Icon />
      </NavigationMenu.Trigger>
      <NavigationMenu.Content>
        <NavigationMenu.Link />
      </NavigationMenu.Content>
    </NavigationMenu.Item>
  </NavigationMenu.List>

  <NavigationMenu.Portal>
    <NavigationMenu.Backdrop />
    <NavigationMenu.Positioner>
      <NavigationMenu.Popup>
        <NavigationMenu.Arrow />
        <NavigationMenu.Viewport />
      </NavigationMenu.Popup>
    </NavigationMenu.Positioner>
  </NavigationMenu.Portal>
</NavigationMenu.Root>

Examples

Nested submenus

Nest a <NavigationMenu.Root> inside a <NavigationMenu.Content> for a second level. The nested root brings its own Portal, Positioner and Viewport, so the submenu floats free of the parent panel.

<script lang="ts">
  import { NavigationMenu } from '@shardsui/svelte/navigation-menu'

  type LinkItem = { href: string; title: string; description: string }

  const topics = [
    {
      href: '#',
      title: 'Frontend',
      description: 'From first components to full working apps.'
    },
    { href: '#', title: 'Design', description: 'Type scale, color tokens, and layout grids.' }
  ]

  const resources = [
    {
      href: '#',
      title: 'Docs',
      description: 'Guides and reference for every feature.'
    },
    { href: '#', title: 'Changelog', description: 'What shipped and when.' },
    { href: '#', title: 'Blog', description: 'Updates, tips, and release notes.' }
  ]
</script>

<NavigationMenu.Root class="min-w-max rounded-lg bg-gray-50 p-1 text-gray-900">
  <NavigationMenu.List class="relative flex gap-px">
    <NavigationMenu.Item>
      <NavigationMenu.Trigger
        class="m-0 box-border flex h-8 items-center justify-center gap-1.5 rounded-md bg-gray-50 px-2 text-sm/6 font-normal text-gray-900 no-underline select-none hover:bg-gray-100 focus-visible:relative focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-gray-950 active:bg-gray-100 data-popup-open:bg-gray-100 sm:px-3 sm:text-sm"
      >
        Products
        <NavigationMenu.Icon
          class="transition-transform duration-200 ease-[ease] data-popup-open:rotate-180"
        >
          {@render chevronDownIcon()}
        </NavigationMenu.Icon>
      </NavigationMenu.Trigger>

      <NavigationMenu.Content
        class="h-full w-[calc(100vw-40px)] p-4 transition-[opacity,transform,translate] duration-(--duration) ease-(--easing) data-ending-style:opacity-0 data-starting-style:opacity-0 data-ending-style:data-[activation-direction=left]:translate-x-1/2 data-starting-style:data-[activation-direction=left]:-translate-x-1/2 data-ending-style:data-[activation-direction=right]:-translate-x-1/2 data-starting-style:data-[activation-direction=right]:translate-x-1/2 min-[32rem]:w-max min-[32rem]:min-w-100"
      >
        <ul class="grid list-none grid-cols-1 gap-0 sm:grid-cols-[12rem_12rem]">
          {#each topics as item (item.title)}
            {@render card(item)}
          {/each}

          <li>
            <NavigationMenu.Root orientation="vertical">
              <NavigationMenu.List>
                <NavigationMenu.Item>
                  <NavigationMenu.Trigger
                    class="relative block w-full rounded-md p-2 text-left text-inherit no-underline hover:bg-gray-100 focus-visible:relative focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-gray-950 data-popup-open:bg-gray-100 sm:p-3"
                  >
                    <span class="m-0 mb-1 text-sm/5 font-normal">Resources</span>
                    <p class="m-0 text-sm/5 text-gray-500">Guides, changelog, and blog posts.</p>
                    <NavigationMenu.Icon
                      class="absolute top-1/2 right-2.5 flex size-4 -translate-y-1/2 items-center justify-center transition-transform duration-200 ease-[ease] data-popup-open:rotate-180"
                    >
                      {@render chevronRightIcon()}
                    </NavigationMenu.Icon>
                  </NavigationMenu.Trigger>
                  <NavigationMenu.Content
                    class="h-full w-[calc(100vw-40px)] p-4 transition-[opacity,transform,translate] duration-(--duration) ease-(--easing) data-ending-style:opacity-0 data-starting-style:opacity-0 data-ending-style:data-[activation-direction=left]:translate-x-1/2 data-starting-style:data-[activation-direction=left]:-translate-x-1/2 data-ending-style:data-[activation-direction=right]:-translate-x-1/2 data-starting-style:data-[activation-direction=right]:translate-x-1/2 min-[32rem]:w-max min-[32rem]:min-w-100"
                  >
                    <ul class="flex max-w-100 flex-col justify-center">
                      {#each resources as item (item.title)}
                        {@render card(item)}
                      {/each}
                    </ul>
                  </NavigationMenu.Content>
                </NavigationMenu.Item>
              </NavigationMenu.List>

              <NavigationMenu.Portal>
                <NavigationMenu.Positioner
                  sideOffset={8}
                  alignOffset={-8}
                  align="end"
                  side="right"
                  class="box-border h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom] duration-(--duration) ease-(--easing) before:absolute before:content-[''] data-instant:transition-none data-[side=bottom]:before:inset-x-0 data-[side=bottom]:before:-top-2.5 data-[side=bottom]:before:h-2.5 data-[side=left]:before:inset-y-0 data-[side=left]:before:-right-2.5 data-[side=left]:before:w-2.5 data-[side=right]:before:inset-y-0 data-[side=right]:before:-left-2.5 data-[side=right]:before:w-2.5 data-[side=top]:before:inset-x-0 data-[side=top]:before:-bottom-2.5 data-[side=top]:before:h-2.5"
                  style="--duration:0.35s;--easing:cubic-bezier(0.22,1,0.36,1);"
                >
                  <NavigationMenu.Popup
                    class="relative h-(--popup-height) w-(--popup-width) origin-(--transform-origin) rounded-lg bg-gray-50 text-gray-900 shadow-lg outline-1 outline-gray-200 transition-[opacity,transform,width,height,scale,translate] duration-(--duration) ease-(--easing) data-ending-style:scale-95 data-ending-style:opacity-0 data-ending-style:duration-150 data-ending-style:ease-[ease] data-starting-style:scale-95 data-starting-style:opacity-0"
                  >
                    <NavigationMenu.Viewport class="relative size-full overflow-hidden" />
                  </NavigationMenu.Popup>
                </NavigationMenu.Positioner>
              </NavigationMenu.Portal>
            </NavigationMenu.Root>
          </li>
        </ul>
      </NavigationMenu.Content>
    </NavigationMenu.Item>
  </NavigationMenu.List>

  <NavigationMenu.Portal>
    <NavigationMenu.Positioner
      sideOffset={10}
      collisionPadding={{ top: 5, bottom: 5, left: 20, right: 20 }}
      class="box-border h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom] duration-(--duration) ease-(--easing) before:absolute before:content-[''] data-instant:transition-none data-[side=bottom]:before:inset-x-0 data-[side=bottom]:before:-top-2.5 data-[side=bottom]:before:h-2.5 data-[side=left]:before:inset-y-0 data-[side=left]:before:-right-2.5 data-[side=left]:before:w-2.5 data-[side=right]:before:inset-y-0 data-[side=right]:before:-left-2.5 data-[side=right]:before:w-2.5 data-[side=top]:before:inset-x-0 data-[side=top]:before:-bottom-2.5 data-[side=top]:before:h-2.5"
      style="--duration:0.35s;--easing:cubic-bezier(0.22,1,0.36,1);"
    >
      <NavigationMenu.Popup
        class="relative h-(--popup-height) w-(--popup-width) origin-(--transform-origin) rounded-lg bg-gray-50 text-gray-900 shadow-lg outline-1 outline-gray-200 transition-[opacity,transform,width,height,scale,translate] duration-(--duration) ease-(--easing) data-ending-style:scale-95 data-ending-style:opacity-0 data-ending-style:duration-150 data-ending-style:ease-[ease] data-starting-style:scale-95 data-starting-style:opacity-0"
      >
        <NavigationMenu.Arrow
          class="flex transition-[left] duration-(--duration) ease-(--easing) data-[side=bottom]:-top-2 data-[side=left]:-right-3.25 data-[side=left]:rotate-90 data-[side=right]:-left-3.25 data-[side=right]:-rotate-90 data-[side=top]:-bottom-2 data-[side=top]:rotate-180"
        >
          {@render arrow()}
        </NavigationMenu.Arrow>
        <NavigationMenu.Viewport class="relative size-full overflow-hidden" />
      </NavigationMenu.Popup>
    </NavigationMenu.Positioner>
  </NavigationMenu.Portal>
</NavigationMenu.Root>

{#snippet chevronDownIcon()}
  <svg class="size-4" viewBox="0 0 24 24" fill="none" aria-hidden="true">
    <path
      d="M5.75 9.5L12 15.75L18.25 9.5"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
  </svg>
{/snippet}

{#snippet chevronRightIcon()}
  <svg class="size-4" viewBox="0 0 24 24" fill="none" aria-hidden="true">
    <path
      d="M9.5 18.25L15.75 12L9.5 5.75"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
  </svg>
{/snippet}

{#snippet card(item: LinkItem)}
  <li>
    <NavigationMenu.Link
      href={item.href}
      class="relative block w-full rounded-md p-2 text-left text-inherit no-underline hover:bg-gray-100 focus-visible:relative focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-gray-950 sm:p-3"
    >
      <h3 class="m-0 mb-1 text-sm/5 font-normal">{item.title}</h3>
      <p class="m-0 text-sm/5 text-gray-500">{item.description}</p>
    </NavigationMenu.Link>
  </li>
{/snippet}

{#snippet arrow()}
  <svg width="20" height="10" viewBox="0 0 20 10" fill="none">
    <path
      d="M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z"
      class="fill-gray-50"
    />
    <path
      d="M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z"
      class="fill-gray-200"
    />
    <path
      d="M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z"
    />
  </svg>
{/snippet}

Nested inline submenus

For second-level navigation that stays inside the same panel, give the nested root only a List and a Viewport — no Portal or Positioner — and seed the open submenu with value.

<script lang="ts">
  import { NavigationMenu } from '@shardsui/svelte/navigation-menu'
  import { MediaQuery } from 'svelte/reactivity'

  const desktop = new MediaQuery('(min-width: 700px)')

  const collections = [
    {
      value: 'getting-started',
      label: 'Getting started',
      hint: 'Start from zero.',
      title: 'Build a foundation',
      description: 'Short guides for your first steps.',
      links: [
        {
          href: '#',
          title: 'Installation',
          description: 'Set up the library in your project.'
        },
        {
          href: '#',
          title: 'Quick start',
          description: 'Compose your first component.'
        }
      ]
    },
    {
      value: 'components',
      label: 'Components',
      hint: 'Explore the parts.',
      title: 'Browse the catalog',
      description: 'Every component, documented and ready to use.',
      links: [
        {
          href: '#',
          title: 'Dialog',
          description: 'Modal overlays with focus management.'
        },
        {
          href: '#',
          title: 'Popover',
          description: 'Floating panels anchored to a trigger.'
        }
      ]
    },
    {
      value: 'advanced',
      label: 'Advanced',
      hint: 'Go deeper.',
      title: 'Customize behavior',
      description: 'Handles, detached triggers, and composition patterns.',
      links: [
        {
          href: '#',
          title: 'Detached triggers',
          description: 'Share one popup across multiple triggers.'
        },
        {
          href: '#',
          title: 'State factories',
          description: 'How internal state is structured.'
        }
      ]
    }
  ]
</script>

<NavigationMenu.Root class="min-w-max rounded-lg bg-gray-50 p-1 text-gray-900">
  <NavigationMenu.List class="relative flex gap-px">
    <NavigationMenu.Item>
      <NavigationMenu.Trigger
        class="m-0 box-border flex h-8 items-center justify-center gap-1.5 rounded-md bg-gray-50 px-2 text-sm/6 font-normal text-gray-900 no-underline select-none hover:bg-gray-100 focus-visible:relative focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-gray-950 active:bg-gray-100 data-popup-open:bg-gray-100 sm:px-3 sm:text-sm"
      >
        Browse
        <NavigationMenu.Icon
          class="transition-transform duration-200 ease-[ease] data-popup-open:rotate-180"
        >
          {@render chevronDownIcon()}
        </NavigationMenu.Icon>
      </NavigationMenu.Trigger>

      <NavigationMenu.Content
        class="h-full w-[calc(100vw-40px)] p-0 transition-[opacity,translate] duration-[calc(var(--duration)*0.5),var(--duration)] ease-[ease,cubic-bezier(0.4,0,0.2,1)] data-ending-style:opacity-0 data-ending-style:duration-[calc(var(--duration)*0.5)] data-ending-style:ease-[ease] data-ending-style:data-[activation-direction=left]:translate-x-8 data-starting-style:data-[activation-direction=left]:-translate-x-8 data-starting-style:data-[activation-direction=left]:opacity-0 data-ending-style:data-[activation-direction=right]:-translate-x-8 data-starting-style:data-[activation-direction=right]:translate-x-8 data-starting-style:data-[activation-direction=right]:opacity-0 min-[700px]:max-w-168.75"
      >
        <NavigationMenu.Root
          class="overflow-hidden text-gray-900"
          orientation={desktop.current ? 'vertical' : 'horizontal'}
          value="getting-started"
        >
          <div
            class="grid grid-cols-1 overflow-clip rounded-lg min-[700px]:grid-cols-[13rem_minmax(0,1fr)]"
          >
            <NavigationMenu.List
              class="m-0 flex list-none flex-row gap-1 overflow-x-auto bg-gray-100 p-4 min-[700px]:box-border min-[700px]:h-(--popup-height) min-[700px]:flex-col min-[700px]:gap-px min-[700px]:overflow-x-visible min-[700px]:overflow-y-auto min-[700px]:border-r min-[700px]:border-r-gray-200 min-[700px]:transition-[height] min-[700px]:duration-(--duration) min-[700px]:ease-(--easing)"
            >
              {#each collections as menu (menu.value)}
                <NavigationMenu.Item value={menu.value}>
                  <NavigationMenu.Trigger
                    class="m-0 box-border flex w-full min-w-40 flex-col items-start gap-0.5 rounded-lg bg-transparent px-3 py-2.5 text-left text-inherit hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-gray-950 data-popup-open:bg-gray-50 data-popup-open:shadow-sm"
                  >
                    <span class="text-sm leading-tight font-normal text-gray-900">{menu.label}</span
                    >
                    <span class="text-sm leading-snug text-gray-500">{menu.hint}</span>
                  </NavigationMenu.Trigger>
                  <NavigationMenu.Content
                    class="min-[700px]:blur-0 h-full translate-x-0 p-7 transition-[opacity,translate,filter] duration-(--duration) ease-(--easing) data-ending-style:opacity-0 data-ending-style:data-[activation-direction=left]:translate-x-1/2 data-starting-style:data-[activation-direction=left]:-translate-x-1/2 data-starting-style:data-[activation-direction=left]:opacity-0 data-ending-style:data-[activation-direction=right]:-translate-x-1/2 data-starting-style:data-[activation-direction=right]:translate-x-1/2 data-starting-style:data-[activation-direction=right]:opacity-0 min-[700px]:p-8 min-[700px]:duration-[calc(var(--duration)*1.35)] min-[700px]:ease-out-expo min-[700px]:data-ending-style:blur-[2px] min-[700px]:data-starting-style:blur-[2px] min-[700px]:data-ending-style:data-[activation-direction=down]:-translate-y-18 min-[700px]:data-starting-style:data-[activation-direction=down]:translate-y-18 min-[700px]:data-starting-style:data-[activation-direction=down]:opacity-0 min-[700px]:data-ending-style:data-[activation-direction=up]:translate-y-18 min-[700px]:data-starting-style:data-[activation-direction=up]:-translate-y-18 min-[700px]:data-starting-style:data-[activation-direction=up]:opacity-0"
                  >
                    <h4 class="m-0 text-base leading-tight font-normal">{menu.title}</h4>
                    <p class="m-0 mt-2.5 text-sm text-gray-500">{menu.description}</p>
                    <ul class="m-0 -mx-2 mt-4 grid list-none gap-0 p-0">
                      {#each menu.links as link (link.title)}
                        <li>
                          <NavigationMenu.Link
                            class="box-border block rounded-lg bg-transparent px-2 py-3 text-inherit no-underline hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-gray-950"
                            href={link.href}
                          >
                            <h5 class="m-0 text-sm/4.5 font-normal">{link.title}</h5>
                            <p class="m-0 mt-1.5 text-sm leading-normal text-gray-500">
                              {link.description}
                            </p>
                          </NavigationMenu.Link>
                        </li>
                      {/each}
                    </ul>
                  </NavigationMenu.Content>
                </NavigationMenu.Item>
              {/each}
            </NavigationMenu.List>
            <NavigationMenu.Viewport
              class="relative min-h-66 overflow-hidden border-t border-gray-200 min-[700px]:border-t-0"
            />
          </div>
        </NavigationMenu.Root>
      </NavigationMenu.Content>
    </NavigationMenu.Item>

    <NavigationMenu.Item>
      <NavigationMenu.Link
        class="m-0 box-border flex h-8 items-center justify-center gap-1.5 rounded-md bg-gray-50 px-2 text-sm/6 font-normal text-gray-900 no-underline select-none hover:bg-gray-100 focus-visible:relative focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-gray-950 active:bg-gray-100 sm:px-3 sm:text-sm"
        href="#">Pricing</NavigationMenu.Link
      >
    </NavigationMenu.Item>
  </NavigationMenu.List>

  <NavigationMenu.Portal>
    <NavigationMenu.Positioner
      sideOffset={10}
      collisionPadding={{ top: 5, bottom: 5, left: 20, right: 20 }}
      collisionAvoidance={{ side: 'none' }}
      class="box-border h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom] duration-(--duration) ease-(--easing) before:absolute before:content-[''] data-instant:transition-none data-[side=bottom]:before:inset-x-0 data-[side=bottom]:before:-top-2.5 data-[side=bottom]:before:h-2.5 data-[side=left]:before:inset-y-0 data-[side=left]:before:-right-2.5 data-[side=left]:before:w-2.5 data-[side=right]:before:inset-y-0 data-[side=right]:before:-left-2.5 data-[side=right]:before:w-2.5 data-[side=top]:before:inset-x-0 data-[side=top]:before:-bottom-2.5 data-[side=top]:before:h-2.5"
      style="--duration:0.35s;--easing:cubic-bezier(0.22,1,0.36,1);"
    >
      <NavigationMenu.Popup
        class="relative h-(--popup-height) w-(--popup-width) origin-(--transform-origin) rounded-lg bg-gray-50 text-gray-900 shadow-lg outline-1 outline-gray-200 transition-[opacity,scale,width,height] duration-(--duration) ease-(--easing) data-ending-style:scale-95 data-ending-style:opacity-0 data-ending-style:transition-[opacity,scale] data-ending-style:duration-150 data-ending-style:ease-[ease] data-starting-style:scale-95 data-starting-style:opacity-0"
      >
        <NavigationMenu.Arrow
          class="flex transition-[left] duration-(--duration) ease-(--easing) data-[side=bottom]:-top-2 data-[side=left]:-right-3.25 data-[side=left]:rotate-90 data-[side=right]:-left-3.25 data-[side=right]:-rotate-90 data-[side=top]:-bottom-2 data-[side=top]:rotate-180"
        >
          {@render arrow()}
        </NavigationMenu.Arrow>
        <NavigationMenu.Viewport class="relative size-full overflow-hidden" />
      </NavigationMenu.Popup>
    </NavigationMenu.Positioner>
  </NavigationMenu.Portal>
</NavigationMenu.Root>

{#snippet chevronDownIcon()}
  <svg class="size-4" viewBox="0 0 24 24" fill="none" aria-hidden="true">
    <path
      d="M5.75 9.5L12 15.75L18.25 9.5"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
  </svg>
{/snippet}

{#snippet arrow()}
  <svg width="20" height="10" viewBox="0 0 20 10" fill="none">
    <path
      d="M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z"
      class="fill-gray-50"
    />
    <path
      d="M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z"
      class="fill-gray-200"
    />
    <path
      d="M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z"
    />
  </svg>
{/snippet}

<NavigationMenu.Link> renders an <a>, so SvelteKit's router intercepts its href for client-side navigation, with no wrapper component needed.

<NavigationMenu.Link href="/docs">Docs</NavigationMenu.Link>

SvelteKit link attributes, such as preloading, pass straight through:

<NavigationMenu.Link href="/docs" data-sveltekit-preload-data="hover">Docs</NavigationMenu.Link>

The menu stays open after a link is clicked; add closeOnClick to dismiss it on navigation.

Large menus

When a panel is taller than the space below its trigger, cap it against --available-height so it shrinks instead of overflowing:

.content,
.popup {
  max-height: var(--available-height);
}

That only works if the content can compress. When it can't, let it scroll:

.content,
.popup {
  max-height: var(--available-height);
}

.content {
  overflow-y: auto;
}

Native scrollbars stay visible while the panel transitions, so Scroll Area is the better fit here. It keeps the scrollbars hidden and lets the Arrow stay centered.

API reference

Root

Groups all parts of the navigation menu. Renders a <nav> element at the root, or a <div> element when nested.

PropTypeDefault
AttributeDescription
data-nestedPresent when this navigation menu is nested in another.
data-openPresent when a menu panel is open.

List

Contains a list of navigation menu items. Renders a <ul> element.

PropTypeDefault
AttributeDescription
data-openPresent when a menu panel is open.

Item

An individual navigation menu item. Renders a <li> element.

PropTypeDefault

Trigger

Opens its item's content on hover or click. Renders a <button> element.

PropTypeDefault
AttributeDescription
data-popup-openPresent while this item's content is showing.
data-pressedPresent alongside data-popup-open, however the content was opened.

Icon

An icon that indicates that the trigger button opens a menu. Renders a <span> element, hidden from assistive technology, falling back to a ▼ glyph when given no children.

PropTypeDefault
AttributeDescription
data-popup-openPresent while this item's content is showing.

Content

The panel for an item, rendered inside <NavigationMenu.Viewport> while that item is active. Renders a <div> element.

PropTypeDefault
AttributeDescription
data-openPresent when this item's content is showing.
data-closedPresent when it is not.
data-activation-directionDirection the newly activated trigger sits in: left, right, up or down.
data-starting-stylePresent when the content is animating in.
data-ending-stylePresent when the content is animating out.

A link to another page or section. Renders an <a> element.

PropTypeDefault
AttributeDescription
data-activePresent when the link is the currently active page.

Backdrop

An overlay displayed beneath the popup. Renders a <div> element.

PropTypeDefault
AttributeDescription
data-openPresent when the popup is open.
data-closedPresent when the popup is closed.
data-starting-stylePresent when the backdrop is animating in.
data-ending-stylePresent when the backdrop is animating out.

Portal

A portal that moves the popup out to <body>, clear of ancestor clipping and stacking. Renders a <div> element.

PropTypeDefault

Positioner

Positions the navigation menu against the currently active trigger. Renders a <div> element.

PropTypeDefault
AttributeDescription
data-openPresent when the popup is open.
data-closedPresent when the popup is closed.
data-sideWhich side of the anchor the popup is on.
data-alignHow the popup is aligned relative to the side.
data-anchor-hiddenPresent when the anchor is hidden.
data-instantPresent when animations should be instant.
CSS VariableDescription
--available-widthAvailable width between the anchor and the viewport edge.
--available-heightAvailable height between the anchor and the viewport edge.
--anchor-widthWidth of the anchor element.
--anchor-heightHeight of the anchor element.
--transform-originTransform origin for scale animations.
--positioner-widthThe current panel's width in pixels. Set for as long as the navigation menu is open.
--positioner-heightThe current panel's height in pixels. Set for as long as the navigation menu is open.

Popup

A container for the navigation menu contents. Renders a <nav> element.

PropTypeDefault
AttributeDescription
data-openPresent when the popup is open.
data-closedPresent when the popup is closed.
data-anchor-hiddenPresent when the anchor is hidden.
data-alignHow the popup is aligned relative to the side.
data-sideWhich side of the anchor the popup is on.
data-starting-stylePresent when the popup is animating in.
data-ending-stylePresent when the popup is animating out.
CSS VariableDescription
--popup-widthThe popup element's width: a pixel value while <NavigationMenu.Viewport> morphs between panels and again while the popup closes, auto once it settles.
--popup-heightThe popup element's height: a pixel value while <NavigationMenu.Viewport> morphs between panels and again while the popup closes, auto once it settles.

Viewport

The clipping viewport of the navigation menu's current content. Renders a <div> element.

PropTypeDefault

The active <NavigationMenu.Content> is rendered here, along with the previous one while it animates out. Both carry the data attributes listed under Content.

Arrow

Displays an element positioned against the anchor. Renders a <div> element.

PropTypeDefault
AttributeDescription
data-openPresent when the popup is open.
data-closedPresent when the popup is closed.
data-sideWhich side of the anchor the popup is on.
data-alignHow the popup is aligned relative to the side.
data-uncenteredPresent when the arrow cannot be centered.