ContentNavigation

An accordion-style navigation component for organizing page links.
This component is only available when the @nuxt/content module is installed.

Usage

Use the navigation prop with the navigation value you get when fetching the navigation of your app.

<script setup lang="ts">
import type { ContentNavigationItem } from '@nuxt/content'

const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
</script>

<template>
  <UContentNavigation :navigation="navigation" highlight />
</template>

Type

Set the type prop to single to only allow one item to be open at a time. Defaults to multiple.

<script setup lang="ts">
const navigation = ref([
  {
    title: 'Guide',
    icon: 'i-lucide-book-open',
    path: '#getting-started',
    children: [
      {
        title: 'Introduction',
        path: '#introduction',
        active: true
      },
      {
        title: 'Installation',
        path: '#installation'
      }
    ]
  },
  {
    title: 'Composables',
    icon: 'i-lucide-database',
    path: '#composables',
    children: [
      {
        title: 'defineShortcuts',
        path: '#defineshortcuts'
      },
      {
        title: 'useModal',
        path: '#usemodal'
      }
    ]
  }
])
</script>

<template>
  <UContentNavigation type="single" />
</template>

Color

Use the color prop to change the color of the navigation links.

<script setup lang="ts">
const navigation = ref([
  {
    title: 'Guide',
    icon: 'i-lucide-book-open',
    path: '#getting-started',
    children: [
      {
        title: 'Introduction',
        path: '#introduction',
        active: true
      },
      {
        title: 'Installation',
        path: '#installation'
      }
    ]
  },
  {
    title: 'Composables',
    icon: 'i-lucide-database',
    path: '#composables',
    children: [
      {
        title: 'defineShortcuts',
        path: '#defineshortcuts'
      },
      {
        title: 'useModal',
        path: '#usemodal'
      }
    ]
  }
])
</script>

<template>
  <UContentNavigation color="neutral" />
</template>

Variant

Use the variant prop to change the variant of the navigation links.

<script setup lang="ts">
const navigation = ref([
  {
    title: 'Guide',
    icon: 'i-lucide-book-open',
    path: '#getting-started',
    children: [
      {
        title: 'Introduction',
        path: '#introduction',
        active: true
      },
      {
        title: 'Installation',
        path: '#installation'
      }
    ]
  },
  {
    title: 'Composables',
    icon: 'i-lucide-database',
    path: '#composables',
    children: [
      {
        title: 'defineShortcuts',
        path: '#defineshortcuts'
      },
      {
        title: 'useModal',
        path: '#usemodal'
      }
    ]
  }
])
</script>

<template>
  <UContentNavigation variant="link" />
</template>

Highlight

Use the highlight prop to display a highlighted border for the active link.

Use the highlight-color prop to change the color of the border. It defaults to the color prop.

<script setup lang="ts">
const navigation = ref([
  {
    title: 'Guide',
    icon: 'i-lucide-book-open',
    path: '#getting-started',
    children: [
      {
        title: 'Introduction',
        path: '#introduction',
        active: true
      },
      {
        title: 'Installation',
        path: '#installation'
      }
    ]
  },
  {
    title: 'Composables',
    icon: 'i-lucide-database',
    path: '#composables',
    children: [
      {
        title: 'defineShortcuts',
        path: '#defineshortcuts'
      },
      {
        title: 'useModal',
        path: '#usemodal'
      }
    ]
  }
])
</script>

<template>
  <UContentNavigation highlight />
</template>

Trailing Icon

<script setup lang="ts">
const navigation = ref([
  {
    title: 'Guide',
    icon: 'i-lucide-book-open',
    path: '#getting-started',
    children: [
      {
        title: 'Introduction',
        path: '#introduction',
        active: true
      },
      {
        title: 'Installation',
        path: '#installation'
      }
    ]
  },
  {
    title: 'Composables',
    icon: 'i-lucide-database',
    path: '#composables',
    children: [
      {
        title: 'defineShortcuts',
        path: '#defineshortcuts'
      },
      {
        title: 'useModal',
        path: '#usemodal'
      }
    ]
  }
])
</script>

<template>
  <UContentNavigation trailing-icon="i-lucide-arrow-up" />
</template>

Examples

Within a layout

Use the ContentNavigation component inside a PageAside component within a layout to display the navigation of the page:

layouts/docs.vue
<script setup lang="ts">
import type { ContentNavigationItem } from '@nuxt/content'

const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
</script>

<template>
  <UPage>
    <template #left>
      <UPageAside>
        <UContentNavigation :navigation="navigation" highlight />
      </UPageAside>
    </template>

    <slot />
  </UPage>
</template>

Within a header

Use the ContentNavigation component inside the content slot of a Header component to display the navigation of the page on mobile:

components/Header.vue
<script setup lang="ts">
import type { ContentNavigationItem } from '@nuxt/content'

const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
</script>

<template>
  <UHeader>
    <template #body>
      <UContentNavigation :navigation="navigation" highlight />
    </template>
  </UHeader>
</template>

API

Props

Prop Default Type
as

'nav'

any

The element or component this component should render as.

defaultOpen

undefined

boolean

When true, the tree will be opened based on the current route. When false, the tree will be closed. When undefined (default), the first item will be opened with type="single" and the first level will be opened with type="multiple".

trailingIcon

appConfig.ui.icons.chevronDown

string

The icon displayed to toggle the accordion.

color

'primary'

"error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral"

variant

'pill'

"link" | "pill"

highlight

false

boolean

Display a line next to the active link.

highlightColor

'primary'

"error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral"

collapsible

true

boolean

When type is "single", allows closing content when clicking trigger for an open item. When type is "multiple", this prop has no effect.

level

0

number

navigation

ContentNavigationLink[]

type

'multiple'

"single" | "multiple"

Determines whether a "single" or "multiple" items can be selected at a time.

This prop will overwrite the inferred type from modelValue and defaultValue.

disabled

false

boolean

When true, prevents the user from interacting with the accordion and all its items

ui

Partial<{ root: string; content: string; list: string; item: string; listWithChildren: string; itemWithChildren: string; trigger: string; link: string; linkLeadingIcon: string; linkTrailing: string; ... 4 more ...; linkTitleExternalIcon: string; }>

Slots

Slot Type
link

{ link: ContentNavigationLink; active?: boolean | undefined; }

link-leading

{ link: ContentNavigationLink; active?: boolean | undefined; }

link-title

{ link: ContentNavigationLink; active?: boolean | undefined; }

link-trailing

{ link: ContentNavigationLink; active?: boolean | undefined; }

Emits

Event Type
update:modelValue

[value: string | string[] | undefined]

Theme

app.config.ts
export default defineAppConfig({
  uiPro: {
    contentNavigation: {
      slots: {
        root: '',
        content: 'data-[state=open]:animate-[accordion-down_200ms_ease-out] data-[state=closed]:animate-[accordion-up_200ms_ease-out] overflow-hidden focus:outline-none',
        list: 'isolate -mx-2.5 -mt-1.5',
        item: 'ps-1.5 -ms-px',
        listWithChildren: 'ms-5 border-s border-(--ui-border)',
        itemWithChildren: 'flex flex-col data-[state=open]:mb-1.5',
        trigger: 'font-semibold',
        link: 'group relative w-full px-2.5 py-1.5 before:inset-y-px before:inset-x-0 flex items-center gap-1.5 text-sm before:absolute before:z-[-1] before:rounded-[calc(var(--ui-radius)*1.5)] focus:outline-none focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2',
        linkLeadingIcon: 'shrink-0 size-5',
        linkTrailing: 'ms-auto inline-flex gap-1.5 items-center',
        linkTrailingBadge: 'shrink-0',
        linkTrailingBadgeSize: 'sm',
        linkTrailingIcon: 'size-5 transform transition-transform duration-200 shrink-0 group-data-[state=open]:rotate-180',
        linkTitle: 'truncate',
        linkTitleExternalIcon: 'size-3 align-top text-(--ui-text-dimmed)'
      },
      variants: {
        color: {
          primary: {
            trigger: 'focus-visible:ring-(--ui-primary)',
            link: 'focus-visible:before:ring-(--ui-primary)'
          },
          secondary: {
            trigger: 'focus-visible:ring-(--ui-secondary)',
            link: 'focus-visible:before:ring-(--ui-secondary)'
          },
          success: {
            trigger: 'focus-visible:ring-(--ui-success)',
            link: 'focus-visible:before:ring-(--ui-success)'
          },
          info: {
            trigger: 'focus-visible:ring-(--ui-info)',
            link: 'focus-visible:before:ring-(--ui-info)'
          },
          warning: {
            trigger: 'focus-visible:ring-(--ui-warning)',
            link: 'focus-visible:before:ring-(--ui-warning)'
          },
          error: {
            trigger: 'focus-visible:ring-(--ui-error)',
            link: 'focus-visible:before:ring-(--ui-error)'
          },
          neutral: {
            trigger: 'focus-visible:ring-(--ui-border-inverted)',
            link: 'focus-visible:before:ring-(--ui-border-inverted)'
          }
        },
        highlightColor: {
          primary: '',
          secondary: '',
          success: '',
          info: '',
          warning: '',
          error: '',
          neutral: ''
        },
        variant: {
          pill: '',
          link: ''
        },
        active: {
          true: {
            link: 'font-medium'
          },
          false: {
            link: 'text-(--ui-text-muted)',
            linkLeadingIcon: 'text-(--ui-text-dimmed)'
          }
        },
        disabled: {
          true: {
            link: 'cursor-not-allowed opacity-75'
          }
        },
        highlight: {
          true: {}
        },
        level: {
          true: {}
        }
      },
      compoundVariants: [
        {
          highlight: true,
          level: true,
          class: {
            link: [
              'after:absolute after:-left-1.5 after:inset-y-0.5 after:block after:w-px after:rounded-full',
              'after:transition-colors'
            ]
          }
        },
        {
          disabled: false,
          active: false,
          variant: 'pill',
          class: {
            link: [
              'hover:text-(--ui-text-highlighted) hover:before:bg-(--ui-bg-elevated)/50 data-[state=open]:text-(--ui-text-highlighted)',
              'transition-colors before:transition-colors'
            ],
            linkLeadingIcon: [
              'group-hover:text-(--ui-text) group-data-[state=open]:text-(--ui-text)',
              'transition-colors'
            ]
          }
        },
        {
          color: 'primary',
          variant: 'pill',
          active: true,
          class: {
            link: 'text-(--ui-primary)',
            linkLeadingIcon: 'text-(--ui-primary) group-data-[state=open]:text-(--ui-primary)'
          }
        },
        {
          color: 'neutral',
          variant: 'pill',
          active: true,
          class: {
            link: 'text-(--ui-text-highlighted)',
            linkLeadingIcon: 'text-(--ui-text-highlighted) group-data-[state=open]:text-(--ui-text-highlighted)'
          }
        },
        {
          variant: 'pill',
          active: true,
          highlight: false,
          class: {
            link: 'before:bg-(--ui-bg-elevated)'
          }
        },
        {
          variant: 'pill',
          active: true,
          highlight: true,
          class: {
            link: [
              'hover:before:bg-(--ui-bg-elevated)/50',
              'before:transition-colors'
            ]
          }
        },
        {
          disabled: false,
          active: false,
          variant: 'link',
          class: {
            link: [
              'hover:text-(--ui-text-highlighted) data-[state=open]:text-(--ui-text-highlighted)',
              'transition-colors'
            ],
            linkLeadingIcon: [
              'group-hover:text-(--ui-text) group-data-[state=open]:text-(--ui-text)',
              'transition-colors'
            ]
          }
        },
        {
          color: 'primary',
          variant: 'link',
          active: true,
          class: {
            link: 'text-(--ui-primary)',
            linkLeadingIcon: 'text-(--ui-primary) group-data-[state=open]:text-(--ui-primary)'
          }
        },
        {
          color: 'neutral',
          variant: 'link',
          active: true,
          class: {
            link: 'text-(--ui-text-highlighted)',
            linkLeadingIcon: 'text-(--ui-text-highlighted) group-data-[state=open]:text-(--ui-text-highlighted)'
          }
        },
        {
          highlightColor: 'primary',
          highlight: true,
          level: true,
          active: true,
          class: {
            link: 'after:bg-(--ui-primary)'
          }
        },
        {
          highlightcolor: 'neutral',
          highlight: true,
          level: true,
          active: true,
          class: {
            link: 'after:bg-(--ui-bg-inverted)'
          }
        }
      ],
      defaultVariants: {
        color: 'primary',
        highlightColor: 'primary',
        variant: 'pill'
      }
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      uiPro: {
        contentNavigation: {
          slots: {
            root: '',
            content: 'data-[state=open]:animate-[accordion-down_200ms_ease-out] data-[state=closed]:animate-[accordion-up_200ms_ease-out] overflow-hidden focus:outline-none',
            list: 'isolate -mx-2.5 -mt-1.5',
            item: 'ps-1.5 -ms-px',
            listWithChildren: 'ms-5 border-s border-(--ui-border)',
            itemWithChildren: 'flex flex-col data-[state=open]:mb-1.5',
            trigger: 'font-semibold',
            link: 'group relative w-full px-2.5 py-1.5 before:inset-y-px before:inset-x-0 flex items-center gap-1.5 text-sm before:absolute before:z-[-1] before:rounded-[calc(var(--ui-radius)*1.5)] focus:outline-none focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2',
            linkLeadingIcon: 'shrink-0 size-5',
            linkTrailing: 'ms-auto inline-flex gap-1.5 items-center',
            linkTrailingBadge: 'shrink-0',
            linkTrailingBadgeSize: 'sm',
            linkTrailingIcon: 'size-5 transform transition-transform duration-200 shrink-0 group-data-[state=open]:rotate-180',
            linkTitle: 'truncate',
            linkTitleExternalIcon: 'size-3 align-top text-(--ui-text-dimmed)'
          },
          variants: {
            color: {
              primary: {
                trigger: 'focus-visible:ring-(--ui-primary)',
                link: 'focus-visible:before:ring-(--ui-primary)'
              },
              secondary: {
                trigger: 'focus-visible:ring-(--ui-secondary)',
                link: 'focus-visible:before:ring-(--ui-secondary)'
              },
              success: {
                trigger: 'focus-visible:ring-(--ui-success)',
                link: 'focus-visible:before:ring-(--ui-success)'
              },
              info: {
                trigger: 'focus-visible:ring-(--ui-info)',
                link: 'focus-visible:before:ring-(--ui-info)'
              },
              warning: {
                trigger: 'focus-visible:ring-(--ui-warning)',
                link: 'focus-visible:before:ring-(--ui-warning)'
              },
              error: {
                trigger: 'focus-visible:ring-(--ui-error)',
                link: 'focus-visible:before:ring-(--ui-error)'
              },
              neutral: {
                trigger: 'focus-visible:ring-(--ui-border-inverted)',
                link: 'focus-visible:before:ring-(--ui-border-inverted)'
              }
            },
            highlightColor: {
              primary: '',
              secondary: '',
              success: '',
              info: '',
              warning: '',
              error: '',
              neutral: ''
            },
            variant: {
              pill: '',
              link: ''
            },
            active: {
              true: {
                link: 'font-medium'
              },
              false: {
                link: 'text-(--ui-text-muted)',
                linkLeadingIcon: 'text-(--ui-text-dimmed)'
              }
            },
            disabled: {
              true: {
                link: 'cursor-not-allowed opacity-75'
              }
            },
            highlight: {
              true: {}
            },
            level: {
              true: {}
            }
          },
          compoundVariants: [
            {
              highlight: true,
              level: true,
              class: {
                link: [
                  'after:absolute after:-left-1.5 after:inset-y-0.5 after:block after:w-px after:rounded-full',
                  'after:transition-colors'
                ]
              }
            },
            {
              disabled: false,
              active: false,
              variant: 'pill',
              class: {
                link: [
                  'hover:text-(--ui-text-highlighted) hover:before:bg-(--ui-bg-elevated)/50 data-[state=open]:text-(--ui-text-highlighted)',
                  'transition-colors before:transition-colors'
                ],
                linkLeadingIcon: [
                  'group-hover:text-(--ui-text) group-data-[state=open]:text-(--ui-text)',
                  'transition-colors'
                ]
              }
            },
            {
              color: 'primary',
              variant: 'pill',
              active: true,
              class: {
                link: 'text-(--ui-primary)',
                linkLeadingIcon: 'text-(--ui-primary) group-data-[state=open]:text-(--ui-primary)'
              }
            },
            {
              color: 'neutral',
              variant: 'pill',
              active: true,
              class: {
                link: 'text-(--ui-text-highlighted)',
                linkLeadingIcon: 'text-(--ui-text-highlighted) group-data-[state=open]:text-(--ui-text-highlighted)'
              }
            },
            {
              variant: 'pill',
              active: true,
              highlight: false,
              class: {
                link: 'before:bg-(--ui-bg-elevated)'
              }
            },
            {
              variant: 'pill',
              active: true,
              highlight: true,
              class: {
                link: [
                  'hover:before:bg-(--ui-bg-elevated)/50',
                  'before:transition-colors'
                ]
              }
            },
            {
              disabled: false,
              active: false,
              variant: 'link',
              class: {
                link: [
                  'hover:text-(--ui-text-highlighted) data-[state=open]:text-(--ui-text-highlighted)',
                  'transition-colors'
                ],
                linkLeadingIcon: [
                  'group-hover:text-(--ui-text) group-data-[state=open]:text-(--ui-text)',
                  'transition-colors'
                ]
              }
            },
            {
              color: 'primary',
              variant: 'link',
              active: true,
              class: {
                link: 'text-(--ui-primary)',
                linkLeadingIcon: 'text-(--ui-primary) group-data-[state=open]:text-(--ui-primary)'
              }
            },
            {
              color: 'neutral',
              variant: 'link',
              active: true,
              class: {
                link: 'text-(--ui-text-highlighted)',
                linkLeadingIcon: 'text-(--ui-text-highlighted) group-data-[state=open]:text-(--ui-text-highlighted)'
              }
            },
            {
              highlightColor: 'primary',
              highlight: true,
              level: true,
              active: true,
              class: {
                link: 'after:bg-(--ui-primary)'
              }
            },
            {
              highlightcolor: 'neutral',
              highlight: true,
              level: true,
              active: true,
              class: {
                link: 'after:bg-(--ui-bg-inverted)'
              }
            }
          ],
          defaultVariants: {
            color: 'primary',
            highlightColor: 'primary',
            variant: 'pill'
          }
        }
      }
    })
  ]
})
Some colors in compoundVariants are omitted for readability. Check out the source code on GitHub.