ContentNavigation

GitLab
Um componente de navegação em estilo accordion para organizar os links da página.
Este componente só está disponível quando o módulo @nuxt/content está instalado.

Uso

Use a prop navigation com o valor navigation que você obtém ao buscar a navegação do seu app.

Tipo

Defina a prop type como single para permitir que apenas um item fique aberto por vez. O padrão é multiple.

<script setup lang="ts">
import type { ContentNavigationLink } from '@nitro/ui'

const navigation = ref<ContentNavigationLink[]>([
  {
    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>
  <NContentNavigation type="single" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { ContentNavigationLink } from '@nitro/ui'

const navigation = ref<ContentNavigationLink[]>([
  {
    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>
  <NContentNavigation type="single" />
</template>

Cor

Use a prop color para alterar a cor dos links de navegação.

<script setup lang="ts">
import type { ContentNavigationLink } from '@nitro/ui'

const navigation = ref<ContentNavigationLink[]>([
  {
    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>
  <NContentNavigation color="neutral" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { ContentNavigationLink } from '@nitro/ui'

const navigation = ref<ContentNavigationLink[]>([
  {
    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>
  <NContentNavigation color="neutral" />
</template>

Variante

Use a prop variant para alterar a variante dos links de navegação.

<script setup lang="ts">
import type { ContentNavigationLink } from '@nitro/ui'

const navigation = ref<ContentNavigationLink[]>([
  {
    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>
  <NContentNavigation variant="link" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { ContentNavigationLink } from '@nitro/ui'

const navigation = ref<ContentNavigationLink[]>([
  {
    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>
  <NContentNavigation variant="link" />
</template>

Destaque

Use a prop highlight para exibir uma borda destacada para o link ativo.

Use a prop highlight-color para alterar a cor da borda. O padrão é o valor da prop color.

<script setup lang="ts">
import type { ContentNavigationLink } from '@nitro/ui'

const navigation = ref<ContentNavigationLink[]>([
  {
    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>
  <NContentNavigation highlight highlight-color="primary" color="primary" variant="pill" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { ContentNavigationLink } from '@nitro/ui'

const navigation = ref<ContentNavigationLink[]>([
  {
    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>
  <NContentNavigation highlight highlight-color="primary" color="primary" variant="pill" />
</template>

Ícone à direita

<script setup lang="ts">
import type { ContentNavigationLink } from '@nitro/ui'

const navigation = ref<ContentNavigationLink[]>([
  {
    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>
  <NContentNavigation trailing-icon="i-lucide-arrow-up" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { ContentNavigationLink } from '@nitro/ui'

const navigation = ref<ContentNavigationLink[]>([
  {
    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>
  <NContentNavigation trailing-icon="i-lucide-arrow-up" />
</template>

Exemplos

Dentro de um layout

Use o componente ContentNavigation dentro de um componente PageAside, em um layout, para exibir a navegação da página:

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

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

<template>
  <NPage>
    <template #left>
      <NPageAside>
        <NContentNavigation :navigation="navigation" highlight />
      </NPageAside>
    </template>

    <slot />
  </NPage>
</template>

Dentro de um cabeçalho

Use o componente ContentNavigation dentro do slot content de um componente Header para exibir a navegação da página no mobile:

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

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

<template>
  <NHeader>
    <template #body>
      <NContentNavigation :navigation="navigation" highlight />
    </template>
  </NHeader>
</template>

API

Props

Prop Default Type
as'nav'any

The element or component this component should render as.

defaultOpenundefinedboolean

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".

trailingIconappConfig.ui.icons.chevronDownany

The icon displayed to toggle the accordion.

color'primary' "primary" | "secondary" | "accent" | "success" | "info" | "warning" | "error" | "neutral"
variant'pill' "pill" | "link"
highlightfalseboolean

Display a line next to the active link.

highlightColor'primary' "primary" | "secondary" | "accent" | "success" | "info" | "warning" | "error" | "neutral"
collapsibletrueboolean

When type is "single", prevents closing the open item when clicking its trigger. When type is "multiple", disables the collapsible behavior.

level0 number
navigation T[]
disabledfalseboolean

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

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.

unmountOnHidetrueboolean

When true, the element will be unmounted on closed state.

ui { root?: SlotClass; content?: SlotClass; list?: SlotClass; item?: SlotClass; listWithChildren?: SlotClass; itemWithChildren?: SlotClass; trigger?: SlotClass; link?: SlotClass; linkLeadingIcon?: SlotClass; linkTrailing?: SlotClass; linkTrailingBadge?: SlotClass; linkTrailingBadgeSize?: SlotClass; linkTrailingIcon?: SlotClass; linkTitle?: SlotClass; linkTitleExternalIcon?: SlotClass; }

Slots

Slot Type
link{ link: T; active?: boolean | undefined; ui: object; }
link-leading{ link: T; active?: boolean | undefined; ui: object; }
link-title{ link: T; active?: boolean | undefined; ui: object; }
link-trailing{ link: T; active?: boolean | undefined; ui: object; }

Emits

Event Type
update:modelValue[value: string | string[] | undefined]

Tema

app.config.ts
export default defineAppConfig({
  ui: {
    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: '',
        listWithChildren: 'ms-5 border-s border-default',
        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-md 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-dimmed'
      },
      variants: {
        color: {
          primary: {
            trigger: 'focus-visible:ring-primary',
            link: 'focus-visible:before:ring-primary'
          },
          secondary: {
            trigger: 'focus-visible:ring-secondary',
            link: 'focus-visible:before:ring-secondary'
          },
          accent: {
            trigger: 'focus-visible:ring-accent',
            link: 'focus-visible:before:ring-accent'
          },
          success: {
            trigger: 'focus-visible:ring-success',
            link: 'focus-visible:before:ring-success'
          },
          info: {
            trigger: 'focus-visible:ring-info',
            link: 'focus-visible:before:ring-info'
          },
          warning: {
            trigger: 'focus-visible:ring-warning',
            link: 'focus-visible:before:ring-warning'
          },
          error: {
            trigger: 'focus-visible:ring-error',
            link: 'focus-visible:before:ring-error'
          },
          neutral: {
            trigger: 'focus-visible:ring-inverted',
            link: 'focus-visible:before:ring-inverted'
          }
        },
        highlightColor: {
          primary: '',
          secondary: '',
          accent: '',
          success: '',
          info: '',
          warning: '',
          error: '',
          neutral: ''
        },
        variant: {
          pill: '',
          link: ''
        },
        active: {
          true: {
            link: 'font-medium'
          },
          false: {
            link: 'text-muted',
            linkLeadingIcon: 'text-dimmed'
          }
        },
        disabled: {
          true: {
            trigger: 'data-[state=open]:text-highlighted'
          }
        },
        highlight: {
          true: {}
        },
        level: {
          true: {
            item: 'ps-1.5 -ms-px',
            itemWithChildren: 'ps-1.5 -ms-px'
          }
        }
      },
      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-highlighted hover:before:bg-elevated/50 data-[state=open]:text-highlighted transition-colors before:transition-colors',
            linkLeadingIcon: 'group-hover:text-default group-data-[state=open]:text-default transition-colors'
          }
        },
        {
          color: 'primary',
          variant: 'pill',
          active: true,
          class: {
            link: 'text-primary',
            linkLeadingIcon: 'text-primary group-data-[state=open]:text-primary'
          }
        },
        {
          color: 'neutral',
          variant: 'pill',
          active: true,
          class: {
            link: 'text-highlighted',
            linkLeadingIcon: 'text-highlighted group-data-[state=open]:text-highlighted'
          }
        },
        {
          variant: 'pill',
          active: true,
          highlight: false,
          class: {
            link: 'before:bg-elevated'
          }
        },
        {
          variant: 'pill',
          active: true,
          highlight: true,
          disabled: false,
          class: {
            link: 'hover:before:bg-elevated/50 before:transition-colors'
          }
        },
        {
          disabled: false,
          active: false,
          variant: 'link',
          class: {
            link: 'hover:text-highlighted data-[state=open]:text-highlighted transition-colors',
            linkLeadingIcon: 'group-hover:text-default group-data-[state=open]:text-default transition-colors'
          }
        },
        {
          color: 'primary',
          variant: 'link',
          active: true,
          class: {
            link: 'text-primary',
            linkLeadingIcon: 'text-primary group-data-[state=open]:text-primary'
          }
        },
        {
          color: 'neutral',
          variant: 'link',
          active: true,
          class: {
            link: 'text-highlighted',
            linkLeadingIcon: 'text-highlighted group-data-[state=open]:text-highlighted'
          }
        },
        {
          highlightColor: 'primary',
          highlight: true,
          level: true,
          active: true,
          class: {
            link: 'after:bg-primary'
          }
        },
        {
          highlightColor: 'neutral',
          highlight: true,
          level: true,
          active: true,
          class: {
            link: 'after: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 '@nitro/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        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: '',
            listWithChildren: 'ms-5 border-s border-default',
            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-md 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-dimmed'
          },
          variants: {
            color: {
              primary: {
                trigger: 'focus-visible:ring-primary',
                link: 'focus-visible:before:ring-primary'
              },
              secondary: {
                trigger: 'focus-visible:ring-secondary',
                link: 'focus-visible:before:ring-secondary'
              },
              accent: {
                trigger: 'focus-visible:ring-accent',
                link: 'focus-visible:before:ring-accent'
              },
              success: {
                trigger: 'focus-visible:ring-success',
                link: 'focus-visible:before:ring-success'
              },
              info: {
                trigger: 'focus-visible:ring-info',
                link: 'focus-visible:before:ring-info'
              },
              warning: {
                trigger: 'focus-visible:ring-warning',
                link: 'focus-visible:before:ring-warning'
              },
              error: {
                trigger: 'focus-visible:ring-error',
                link: 'focus-visible:before:ring-error'
              },
              neutral: {
                trigger: 'focus-visible:ring-inverted',
                link: 'focus-visible:before:ring-inverted'
              }
            },
            highlightColor: {
              primary: '',
              secondary: '',
              accent: '',
              success: '',
              info: '',
              warning: '',
              error: '',
              neutral: ''
            },
            variant: {
              pill: '',
              link: ''
            },
            active: {
              true: {
                link: 'font-medium'
              },
              false: {
                link: 'text-muted',
                linkLeadingIcon: 'text-dimmed'
              }
            },
            disabled: {
              true: {
                trigger: 'data-[state=open]:text-highlighted'
              }
            },
            highlight: {
              true: {}
            },
            level: {
              true: {
                item: 'ps-1.5 -ms-px',
                itemWithChildren: 'ps-1.5 -ms-px'
              }
            }
          },
          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-highlighted hover:before:bg-elevated/50 data-[state=open]:text-highlighted transition-colors before:transition-colors',
                linkLeadingIcon: 'group-hover:text-default group-data-[state=open]:text-default transition-colors'
              }
            },
            {
              color: 'primary',
              variant: 'pill',
              active: true,
              class: {
                link: 'text-primary',
                linkLeadingIcon: 'text-primary group-data-[state=open]:text-primary'
              }
            },
            {
              color: 'neutral',
              variant: 'pill',
              active: true,
              class: {
                link: 'text-highlighted',
                linkLeadingIcon: 'text-highlighted group-data-[state=open]:text-highlighted'
              }
            },
            {
              variant: 'pill',
              active: true,
              highlight: false,
              class: {
                link: 'before:bg-elevated'
              }
            },
            {
              variant: 'pill',
              active: true,
              highlight: true,
              disabled: false,
              class: {
                link: 'hover:before:bg-elevated/50 before:transition-colors'
              }
            },
            {
              disabled: false,
              active: false,
              variant: 'link',
              class: {
                link: 'hover:text-highlighted data-[state=open]:text-highlighted transition-colors',
                linkLeadingIcon: 'group-hover:text-default group-data-[state=open]:text-default transition-colors'
              }
            },
            {
              color: 'primary',
              variant: 'link',
              active: true,
              class: {
                link: 'text-primary',
                linkLeadingIcon: 'text-primary group-data-[state=open]:text-primary'
              }
            },
            {
              color: 'neutral',
              variant: 'link',
              active: true,
              class: {
                link: 'text-highlighted',
                linkLeadingIcon: 'text-highlighted group-data-[state=open]:text-highlighted'
              }
            },
            {
              highlightColor: 'primary',
              highlight: true,
              level: true,
              active: true,
              class: {
                link: 'after:bg-primary'
              }
            },
            {
              highlightColor: 'neutral',
              highlight: true,
              level: true,
              active: true,
              class: {
                link: 'after:bg-inverted'
              }
            }
          ],
          defaultVariants: {
            color: 'primary',
            highlightColor: 'primary',
            variant: 'pill'
          }
        }
      }
    })
  ]
})
Some colors in compoundVariants are omitted for readability. Check out the source code on GitLab.

Changelog

No recent changes