---
name: navigation-shop
title: Navigation Shop
url: https://ui.frontic.com/components/sections/navigation-shop.md
tier: sections
install: npx @frontic/ui add navigation-shop
files:
  - components/sections/navigation-shop/NavigationShop.vue
  - components/sections/navigation-shop/index.ts
  - components/sections/navigation-shop/types.ts
registryDependencies:
  - button
  - image
  - mega-menu
  - separator
  - sheet
  - tabs
dependencies:
  - "@lucide/vue"
  - "@vueuse/core"
  - class-variance-authority
---

# Navigation Shop

Store navigation whose categories open full-width flyouts of link columns and featured tiles, laid out beside, above or captioned, and fold into a tabbed menu sheet on narrow screens.

## Install

```sh
npx @frontic/ui add navigation-shop
```

Built from: [Button](https://ui.frontic.com/components/ui/button.md), [Image](https://ui.frontic.com/components/ui/image.md), [Mega Menu](https://ui.frontic.com/components/ui/mega-menu.md), [Separator](https://ui.frontic.com/components/ui/separator.md), [Sheet](https://ui.frontic.com/components/ui/sheet.md), [Tabs](https://ui.frontic.com/components/ui/tabs.md)

Used by: [Category Grid](https://ui.frontic.com/components/templates/category-grid.md), [Header Centered](https://ui.frontic.com/components/templates/header-centered.md), [Header Shop](https://ui.frontic.com/components/templates/header-shop.md)

npm packages: `@lucide/vue`, `@vueuse/core`, `class-variance-authority`

## Examples

```vue
<script setup lang="ts">
import { useElementSize, useMutationObserver } from '@vueuse/core'
import { computed, ref, shallowRef, useTemplateRef, watch } from 'vue'
import { NavigationShop, type NavigationCategory, type NavigationLink } from '@/components/sections/navigation-shop'

const image = (name: string) => `https://static.frontic.com/cdn-cgi/image/width=640,format=auto/ui/products/${name}.jpg`

const categories: NavigationCategory[] = [
  {
    name: 'Women',
    featured: [
      { name: 'Mountain View Ski Goggles', href: '#', imageSrc: image('mountain-view-ski-goggles'), imageAlt: 'Mountain View Ski Goggles' },
      { name: 'Winterwonder Parka', href: '#', imageSrc: image('winterwonder-parka'), imageAlt: 'Winterwonder Parka' },
      { name: 'Northern Light Hybrid Jacket', href: '#', imageSrc: image('northern-light-hybrid-jacket-lavender'), imageAlt: 'Northern Light Hybrid Jacket' },
    ],
    columns: [
      {
        name: 'Winter',
        links: [
          { name: 'Jackets', href: '#' },
          { name: 'Beanies', href: '#' },
          { name: 'Goggles', href: '#' },
          { name: 'Helmets', href: '#' },
          { name: 'Gloves', href: '#' },
        ],
      },
      {
        name: 'Equipment',
        links: [{ name: 'Backpacks', href: '#' }],
      },
    ],
  },
  {
    name: 'Men',
    featured: [
      { name: 'Snow Falcon Glide', href: '#', imageSrc: image('snow-falcon-glide'), imageAlt: 'Snow Falcon Glide' },
      { name: 'Frost Escape Parka', href: '#', imageSrc: image('frost-escape-parka'), imageAlt: 'Frost Escape Parka' },
      { name: 'TrailMaster 22', href: '#', imageSrc: image('trailmaster-22'), imageAlt: 'TrailMaster 22' },
    ],
    columns: [
      {
        name: 'Winter',
        links: [
          { name: 'Jackets', href: '#' },
          { name: 'Beanies', href: '#' },
          { name: 'Goggles', href: '#' },
          { name: 'Helmets', href: '#' },
        ],
      },
      {
        name: 'Equipment',
        links: [{ name: 'Backpacks', href: '#' }],
      },
    ],
  },
  { name: 'Equipment', href: '#' },
]

const pages: NavigationLink[] = [{ name: 'Sale', href: '#', highlight: true }]

// Shown open: the Women flyout where the bar has room, the sheet where it folds into the menu button.
const menu = ref('Women')
const open = ref(false)
const stage = useTemplateRef<HTMLElement>('stage')
const { width } = useElementSize(stage)
const folded = computed(() => width.value > 0 && width.value < 1024)
watch(folded, (value) => (open.value = value))
// The flyout hangs below the bar, outside the demo's height; the demo grows to the tallest it has held, so closing one never shrinks the page under the reader.
const viewport = shallowRef<HTMLElement | null>(null)
useMutationObserver(stage, () => (viewport.value = stage.value?.querySelector<HTMLElement>('[data-slot="navigation-shop-viewport"]') ?? null), { childList: true, subtree: true })
const barSize = useElementSize(useTemplateRef('bar'))
const flyout = useElementSize(viewport)
const tallest = ref(0)
watch([barSize.height, flyout.height], ([barHeight, flyoutHeight]) => (tallest.value = Math.max(tallest.value, barHeight + flyoutHeight)))
</script>

<template>
  <div ref="stage" class="@container min-h-130" :style="{ height: `${tallest}px` }">
    <div ref="bar" class="relative">
      <NavigationShop v-model:menu="menu" v-model:open="open" :categories="categories" :pages="pages" />
    </div>
  </div>
</template>
```

## API reference

### NavigationShop

Shows store navigation with full-width flyouts of link columns and featured tiles, laid out beside, above or captioned, plus a mobile menu. Renders a `<div>`.

`data-slot="navigation-shop"` · `NavigationShop.vue`

Styling: `navigation-shop-page`, which extends [`nav-trigger`](https://ui.frontic.com/DESIGN.md#nav-trigger); `navigation-shop-trigger`, which extends [`nav-trigger`](https://ui.frontic.com/DESIGN.md#nav-trigger); [`link`](https://ui.frontic.com/DESIGN.md#link); [`hint`](https://ui.frontic.com/DESIGN.md#hint); [`line-title`](https://ui.frontic.com/DESIGN.md#line-title); [`section-title`](https://ui.frontic.com/DESIGN.md#section-title). Change them there and every part that extends them follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `layout` | `NavigationLayout`: `'beside' \| 'above' \| 'captioned'` | `'beside'` | Beside puts the link columns left of square tiles; above sets a row of tiles over full-width columns; captioned leads with a wide tile under caption overlays and underlines the triggers. |
| `categories` (required) | `NavigationCategory[]` |  | The top-level categories; one with featured tiles or link columns opens a flyout, one without is a plain link. |
| `pages` | `NavigationLink[]` | `[]` | Plain links after the categories, such as Sale or Journal. |
| `menuLabel` | `string` | `'Open menu'` | Screen-reader name of the button that opens the mobile menu. |
| `sheetTitle` | `string` | `'Navigation menu'` | Screen-reader title of the mobile menu. |
| `featuredCta` | `string` | `'Shop now'` | Shown under each featured tile's name, e.g. `Shop now`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `open` | `boolean` | `false` | Whether the mobile menu is open. |
| `menu` | `string` | `''` | The category whose flyout is open, by name; empty when none is. |

| Event | Payload | Description |
| --- | --- | --- |
| `update:open` | `value: boolean` | Fires with the new value when `open` changes; `v-model:open` binds it. |
| `update:menu` | `value: string` | Fires with the new value when `menu` changes; `v-model:menu` binds it. |

| Slot | Slot props | Description |
| --- | --- | --- |
| `mobile-footer` | `{ close: () => void; }` | Adds content below the mobile menu; gets `close` to shut the menu sheet. |

## Exports

```ts
import { type NavigationLink, type NavigationFeatured, type NavigationColumn, type NavigationCategory, type NavigationLayout, type NavigationProps } from '@/components/sections/navigation-shop'
```

- `NavigationLink`: below
- `NavigationFeatured`: below
- `NavigationColumn`: below
- `NavigationCategory`: below
- `NavigationLayout`: `'beside' \| 'above' \| 'captioned'`
- `NavigationProps`: below

```ts
interface NavigationLink {
  name: string
  href: string
  highlight?: boolean
}

interface NavigationFeatured {
  name: string
  href: string
  imageSrc: string
  imageAlt: string
  /** In the captioned layout, spans both columns at 2:1; the first tile does when left out. */
  wide?: boolean
}

interface NavigationColumn {
  name: string
  links: NavigationLink[]
}

interface NavigationCategory {
  name: string
  href?: string
  featured?: NavigationFeatured[]
  columns?: NavigationColumn[]
}

interface NavigationProps {
  /** Beside puts the link columns left of square tiles; above sets a row of tiles over full-width columns; captioned leads with a wide tile under caption overlays and underlines the triggers. */
  layout?: NavigationLayout
  /** The top-level categories; one with featured tiles or link columns opens a flyout, one without is a plain link. */
  categories: NavigationCategory[]
  /** Plain links after the categories, such as Sale or Journal. */
  pages?: NavigationLink[]
  /** Screen-reader name of the button that opens the mobile menu. */
  menuLabel?: string
  /** Screen-reader title of the mobile menu. */
  sheetTitle?: string
  /** Shown under each featured tile's name, e.g. `Shop now`. */
  featuredCta?: string
}
```

## Styling

| Part | Recipe |
| --- | --- |
| `root` | [`navigation-shop-root`](https://ui.frontic.com/DESIGN.md#navigation-shop-root) |
| `trigger` | [`navigation-shop-trigger`](https://ui.frontic.com/DESIGN.md#navigation-shop-trigger) |
| `page` | [`navigation-shop-page`](https://ui.frontic.com/DESIGN.md#navigation-shop-page) |
| `content` | [`navigation-shop-content`](https://ui.frontic.com/DESIGN.md#navigation-shop-content) |
| `viewport` | [`navigation-shop-viewport`](https://ui.frontic.com/DESIGN.md#navigation-shop-viewport) |
| `featured-image` | [`navigation-shop-featured-image`](https://ui.frontic.com/DESIGN.md#navigation-shop-featured-image) |
| `caption` | [`navigation-shop-caption`](https://ui.frontic.com/DESIGN.md#navigation-shop-caption) |
| `columns` | [`navigation-shop-columns`](https://ui.frontic.com/DESIGN.md#navigation-shop-columns) |
| `link` | [`link`](https://ui.frontic.com/DESIGN.md#link) |
| `hint` | [`hint`](https://ui.frontic.com/DESIGN.md#hint) |
| `focus-ring` | [`focus-ring`](https://ui.frontic.com/DESIGN.md#focus-ring) |
| `line-title` | [`line-title`](https://ui.frontic.com/DESIGN.md#line-title) |
| `section-title` | [`section-title`](https://ui.frontic.com/DESIGN.md#section-title) |

States with a look of their own: hover and focus.

## Source

`components/sections/navigation-shop/NavigationShop.vue`

```vue
<script lang="ts">
/** Shows store navigation with full-width flyouts of link columns and featured tiles, laid out beside, above or captioned, plus a mobile menu. */
export default {}
</script>

<script setup lang="ts">
import { MenuIcon } from '@lucide/vue'

import type { HTMLAttributes } from 'vue'
import type { NavigationCategory, NavigationColumn, NavigationFeatured, NavigationProps } from './types'
import { createReusableTemplate } from '@vueuse/core'
import { cva } from 'class-variance-authority'
import { computed } from 'vue'
import { Button } from '@library/components/ui/button'
import { Image } from '@library/components/ui/image'
import { MegaMenu, MegaMenuContent, MegaMenuItem, MegaMenuLink, MegaMenuList, MegaMenuTrigger, MegaMenuViewport } from '@library/components/ui/mega-menu'
import { Separator } from '@library/components/ui/separator'
import { Sheet, SheetContent, SheetTitle, SheetTrigger } from '@library/components/ui/sheet'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@library/components/ui/tabs'

const props = withDefaults(defineProps<NavigationProps & { class?: HTMLAttributes['class'] }>(), {
  layout: 'beside',
  pages: () => [],
  menuLabel: 'Open menu',
  sheetTitle: 'Navigation menu',
  featuredCta: 'Shop now',
})
/** Whether the mobile menu is open. */
const open = defineModel<boolean>('open', { default: false })
/** The category whose flyout is open, by name; empty when none is. */
const menu = defineModel<string>('menu', { default: '' })
defineSlots<{
  /** Adds content below the mobile menu; gets `close` to shut the menu sheet. */
  'mobile-footer'?: (props: { close: () => void }) => unknown
}>()

const trigger = cva('fui-navigation-shop-trigger', {
  variants: { layout: { beside: 'fui-navigation-shop-trigger-beside', above: 'fui-navigation-shop-trigger-above', captioned: 'fui-navigation-shop-trigger-captioned' } },
  defaultVariants: { layout: 'beside' },
})
const page = cva('fui-navigation-shop-page inline-flex items-center justify-center', {
  variants: {
    layout: { beside: 'fui-navigation-shop-page-beside', above: 'fui-navigation-shop-page-above', captioned: 'fui-navigation-shop-page-captioned' },
    tone: { highlight: 'fui-navigation-shop-page-highlight' },
  },
  defaultVariants: { layout: 'beside' },
})
const content = cva('fui-navigation-shop-content', {
  variants: { layout: { beside: 'fui-navigation-shop-content-beside', above: 'fui-navigation-shop-content-above', captioned: 'fui-navigation-shop-content-captioned' } },
  defaultVariants: { layout: 'beside' },
})
const viewport = cva('fui-navigation-shop-viewport', {
  variants: { layout: { beside: 'fui-navigation-shop-viewport-beside', above: 'fui-navigation-shop-viewport-above', captioned: 'fui-navigation-shop-viewport-captioned' } },
  defaultVariants: { layout: 'beside' },
})
const featuredImage = cva('fui-navigation-shop-featured-image overflow-hidden', {
  variants: {
    layout: { beside: 'fui-navigation-shop-featured-image-beside', above: 'fui-navigation-shop-featured-image-above', captioned: 'fui-navigation-shop-featured-image-captioned' },
  },
  defaultVariants: { layout: 'beside' },
})

const hasMenu = (category: NavigationCategory) => !!(category.featured?.length || category.columns?.length)
const menus = computed(() => props.categories.filter(hasMenu))
const links = computed<{ name: string; href?: string; highlight?: boolean }[]>(() => [...props.categories.filter((category) => !hasMenu(category)), ...props.pages])
const GRID = ['grid-cols-1', 'grid-cols-1', 'grid-cols-2', 'grid-cols-3']
/** A captioned flyout leads with a wide tile; the other layouts keep every tile square. */
const wide = (item: NavigationFeatured, at: number) => props.layout === 'captioned' && (item.wide ?? at === 0)
/** The captioned flyout's three link columns, two stacked in each and the rest in the last: five read [[0, 1], [2, 3], [4]]. */
function groupColumns(columns: NavigationColumn[] = []): NavigationColumn[][] {
  const groups: NavigationColumn[][] = [[], [], []]
  columns.forEach((column, i) => groups[Math.min(Math.floor(i / 2), 2)]!.push(column))
  return groups.filter((group) => group.length)
}
const close = () => {
  open.value = false
}

const [DefineColumn, ReuseColumn] = createReusableTemplate<{ column: NavigationColumn }>()
const [DefineTile, ReuseTile] = createReusableTemplate<{ item: NavigationFeatured }>()
</script>

<template>
  <DefineColumn v-slot="{ column }">
    <div>
      <p data-slot="section-title" class="fui-section-title">{{ column.name }}</p>
      <ul role="list" class="mt-4 space-y-3">
        <li v-for="link in column.links" :key="link.name" class="flex">
          <MegaMenuLink as-child>
            <a :href="link.href" data-slot="link" class="fui-link">{{ link.name }}</a>
          </MegaMenuLink>
        </li>
      </ul>
    </div>
  </DefineColumn>
  <DefineTile v-slot="{ item }">
    <div class="group relative">
      <div data-slot="navigation-shop-featured-image" :class="[featuredImage({ layout }), 'aspect-square']">
        <Image :src="item.imageSrc" :alt="item.imageAlt" class="size-full object-cover transition-transform duration-300 group-hover:scale-105" />
      </div>
      <div class="mt-4 flex flex-col gap-1">
        <MegaMenuLink as-child>
          <a :href="item.href" data-slot="line-title" class="fui-line-title">
            <span aria-hidden="true" class="absolute inset-0 z-10" />
            {{ item.name }}
          </a>
        </MegaMenuLink>
        <p aria-hidden="true" data-slot="hint" class="fui-hint">{{ featuredCta }}</p>
      </div>
    </div>
  </DefineTile>

  <div data-slot="navigation-shop" :class="['fui-navigation-shop-root', props.class]">
    <Sheet v-model:open="open">
      <SheetTrigger as-child>
        <Button variant="ghost" color="neutral" size="icon-md" :aria-label="menuLabel" class="@5xl:hidden">
          <MenuIcon data-icon />
        </Button>
      </SheetTrigger>
      <SheetContent side="left" class="w-full max-w-xs overflow-y-auto p-0">
        <SheetTitle class="sr-only">{{ sheetTitle }}</SheetTitle>
        <Tabs v-if="menus.length" :default-value="menus[0]!.name" class="mt-2">
          <div>
            <TabsList variant="line" class="w-full ps-4 pe-14">
              <TabsTrigger v-for="category in menus" :key="category.name" :value="category.name" variant="line" class="flex-1">{{ category.name }}</TabsTrigger>
            </TabsList>
            <Separator class="-mt-px" />
          </div>
          <TabsContent v-for="category in menus" :key="category.name" :value="category.name" class="space-y-8 px-4 pt-6 pb-8">
            <div v-if="category.featured?.length" class="grid grid-cols-2 gap-x-4 gap-y-6">
              <div v-for="(item, at) in category.featured" :key="item.name" :class="['group relative', { 'col-span-2': wide(item, at) }]">
                <div data-slot="navigation-shop-featured-image" :class="[featuredImage({ layout }), wide(item, at) ? 'aspect-2/1' : 'aspect-square']">
                  <Image :src="item.imageSrc" :alt="item.imageAlt" class="size-full object-cover transition-transform duration-300 group-hover:scale-105" />
                </div>
                <div class="mt-3 flex flex-col gap-1">
                  <a :href="item.href" data-slot="line-title" class="fui-line-title outline-none focus-visible:fui-focus-ring" @click="close">
                    <span aria-hidden="true" class="absolute inset-0 z-10" />
                    {{ item.name }}
                  </a>
                  <p aria-hidden="true" data-slot="hint" class="fui-hint">{{ featuredCta }}</p>
                </div>
              </div>
            </div>
            <div v-for="column in category.columns" :key="column.name">
              <p data-slot="section-title" class="fui-section-title">{{ column.name }}</p>
              <ul role="list" class="mt-2 flex flex-col">
                <li v-for="link in column.links" :key="link.name" data-slot="link" class="fui-link flow-root">
                  <a :href="link.href" data-slot="focus-ring" class="block py-2 outline-none focus-visible:fui-focus-ring" @click="close">{{ link.name }}</a>
                </li>
              </ul>
            </div>
          </TabsContent>
        </Tabs>
        <div v-if="links.length">
          <Separator v-if="menus.length" />
          <div class="flex flex-col px-4 py-4">
            <div v-for="link in links" :key="link.name" data-slot="section-title" class="fui-section-title flow-root">
              <component :is="link.href ? 'a' : 'span'" :href="link.href" data-slot="focus-ring" class="block py-2 outline-none focus-visible:fui-focus-ring" @click="close">{{ link.name }}</component>
            </div>
          </div>
        </div>
        <div v-if="$slots['mobile-footer']">
          <Separator />
          <div class="space-y-6 px-4 py-6">
            <slot name="mobile-footer" :close="close" />
          </div>
        </div>
      </SheetContent>
    </Sheet>

    <MegaMenu v-model="menu" class="static hidden @5xl:flex @5xl:w-auto @5xl:self-stretch">
      <MegaMenuList :class="layout === 'captioned' && 'gap-8'">
        <MegaMenuItem v-for="category in categories" :key="category.name" :value="category.name">
          <MegaMenuLink v-if="!hasMenu(category) && category.href" as-child>
            <a :href="category.href" data-slot="navigation-shop-page" :class="page({ layout })">{{ category.name }}</a>
          </MegaMenuLink>
          <span v-else-if="!hasMenu(category)" data-slot="navigation-shop-page" :class="page({ layout, class: 'pointer-events-none' })">{{ category.name }}</span>
          <MegaMenuTrigger v-else data-slot="navigation-shop-trigger" :chevron="layout !== 'captioned'" :class="[trigger({ layout }), layout === 'captioned' && 'duration-200 ease-out']">{{
            category.name
          }}</MegaMenuTrigger>
          <MegaMenuContent v-if="hasMenu(category)" data-slot="navigation-shop-content" :class="content({ layout })">
            <div class="mx-auto max-w-7xl px-4 @2xl:px-6 @5xl:px-8">
              <div v-if="layout === 'beside'" class="grid grid-cols-2 gap-x-8 gap-y-10 py-10">
                <div :class="['col-start-2 grid gap-x-6 gap-y-10', (category.featured?.length ?? 0) > 2 ? 'grid-cols-3' : 'grid-cols-2']">
                  <ReuseTile v-for="item in category.featured" :key="item.name" :item="item" />
                </div>
                <div :class="['row-start-1 grid content-start gap-x-8 gap-y-10', GRID[Math.min(category.columns?.length ?? 0, 3)]]">
                  <ReuseColumn v-for="column in category.columns" :key="column.name" :column="column" />
                </div>
              </div>
              <div v-else-if="layout === 'above'" class="py-10">
                <div v-if="category.featured?.length" :class="['grid gap-6', GRID[Math.min(category.featured.length, 3)]]">
                  <ReuseTile v-for="item in category.featured" :key="item.name" :item="item" />
                </div>
                <Separator v-if="category.featured?.length && category.columns?.length" class="mt-8" />
                <div v-if="category.columns?.length" data-slot="navigation-shop-columns" :class="['fui-navigation-shop-columns', { 'pt-8': category.featured?.length }]">
                  <div :class="['grid gap-x-8 gap-y-10', GRID[Math.min(category.columns.length, 3)]]">
                    <ReuseColumn v-for="column in category.columns" :key="column.name" :column="column" />
                  </div>
                </div>
              </div>
              <div v-else class="grid grid-cols-2 gap-x-8 gap-y-10 py-10">
                <div class="grid grid-cols-2 gap-6">
                  <div
                    v-for="(item, at) in category.featured"
                    :key="item.name"
                    data-slot="navigation-shop-featured-image"
                    :class="[featuredImage({ layout }), 'group relative', { 'col-span-2': wide(item, at) }]"
                  >
                    <Image
                      :src="item.imageSrc"
                      :alt="item.imageAlt"
                      :class="['w-full object-cover transition-transform duration-300 group-hover:scale-105', wide(item, at) ? 'aspect-2/1' : 'aspect-square']"
                    />
                    <div class="absolute inset-0 flex flex-col justify-end">
                      <div data-slot="navigation-shop-caption" class="fui-navigation-shop-caption">
                        <div class="flex flex-col gap-0.5">
                          <MegaMenuLink as-child>
                            <a :href="item.href" data-slot="line-title" class="fui-line-title">
                              <span aria-hidden="true" class="absolute inset-0" />
                              {{ item.name }}
                            </a>
                          </MegaMenuLink>
                          <p aria-hidden="true" data-slot="hint" class="fui-hint">{{ featuredCta }}</p>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
                <div class="grid grid-cols-3 gap-x-8 gap-y-10">
                  <div v-for="(group, at) in groupColumns(category.columns)" :key="at" class="space-y-10">
                    <ReuseColumn v-for="column in group" :key="column.name" :column="column" />
                  </div>
                </div>
              </div>
            </div>
          </MegaMenuContent>
        </MegaMenuItem>
        <MegaMenuItem v-for="link in pages" :key="link.name">
          <MegaMenuLink as-child>
            <a :href="link.href" data-slot="navigation-shop-page" :class="page({ layout, tone: link.highlight ? 'highlight' : undefined })">{{ link.name }}</a>
          </MegaMenuLink>
        </MegaMenuItem>
      </MegaMenuList>
      <MegaMenuViewport data-slot="navigation-shop-viewport" :class="viewport({ layout })" />
    </MegaMenu>
  </div>
</template>
```

`components/sections/navigation-shop/types.ts`

```ts
export interface NavigationLink {
  name: string
  href: string
  highlight?: boolean
}

export interface NavigationFeatured {
  name: string
  href: string
  imageSrc: string
  imageAlt: string
  /** In the captioned layout, spans both columns at 2:1; the first tile does when left out. */
  wide?: boolean
}

export interface NavigationColumn {
  name: string
  links: NavigationLink[]
}

export interface NavigationCategory {
  name: string
  href?: string
  featured?: NavigationFeatured[]
  columns?: NavigationColumn[]
}

/** How a flyout sets its featured tiles against its link columns. */
export type NavigationLayout = 'beside' | 'above' | 'captioned'

export interface NavigationProps {
  /** Beside puts the link columns left of square tiles; above sets a row of tiles over full-width columns; captioned leads with a wide tile under caption overlays and underlines the triggers. */
  layout?: NavigationLayout
  /** The top-level categories; one with featured tiles or link columns opens a flyout, one without is a plain link. */
  categories: NavigationCategory[]
  /** Plain links after the categories, such as Sale or Journal. */
  pages?: NavigationLink[]
  /** Screen-reader name of the button that opens the mobile menu. */
  menuLabel?: string
  /** Screen-reader title of the mobile menu. */
  sheetTitle?: string
  /** Shown under each featured tile's name, e.g. `Shop now`. */
  featuredCta?: string
}
```
