Sidebar
Utilisation
Sur le bureau, il rend en ligne et peut être rétracté; sur mobile, il ouvre un composant Modal, Slideover ou Drawer.
Use the header, default and footer slots to customize the content of the sidebar. The v-model:open directive is viewport-aware: on desktop it controls the expanded/collapsed state, on mobile it controls the menu.
<script setup lang="ts">
import type { DropdownMenuItem, NavigationMenuItem } from '@nuxt/ui'
const open = ref(true)
const colorMode = useColorMode()
const teams = ref([
{
label: 'Nuxt',
avatar: {
src: 'https://github.com/nuxt.png',
alt: 'Nuxt'
}
},
{
label: 'Vue',
avatar: {
src: 'https://github.com/vuejs.png',
alt: 'Vue'
}
},
{
label: 'UnJS',
avatar: {
src: 'https://github.com/unjs.png',
alt: 'UnJS'
}
}
])
const selectedTeam = ref(teams.value[0])
const teamsItems = computed<DropdownMenuItem[][]>(() => {
return [
teams.value.map((team, index) => ({
...team,
kbds: ['meta', String(index + 1)],
onSelect() {
selectedTeam.value = team
}
})),
[
{
label: 'Create team',
icon: 'i-lucide-circle-plus'
}
]
]
})
function getItems(state: 'collapsed' | 'expanded') {
return [
{
label: 'Inbox',
icon: 'i-lucide-inbox',
badge: '4'
},
{
label: 'Issues',
icon: 'i-lucide-square-dot'
},
{
label: 'Activity',
icon: 'i-lucide-square-activity'
},
{
label: 'Settings',
icon: 'i-lucide-settings',
defaultOpen: true,
children:
state === 'expanded'
? [
{
label: 'General',
icon: 'i-lucide-house'
},
{
label: 'Team',
icon: 'i-lucide-users'
},
{
label: 'Billing',
icon: 'i-lucide-credit-card'
}
]
: []
}
] satisfies NavigationMenuItem[]
}
const user = ref({
name: 'Benjamin Canac',
avatar: {
src: 'https://github.com/benjamincanac.png',
alt: 'Benjamin Canac'
}
})
const userItems = computed<DropdownMenuItem[][]>(() => [
[
{
label: 'Profile',
icon: 'i-lucide-user'
},
{
label: 'Billing',
icon: 'i-lucide-credit-card'
},
{
label: 'Settings',
icon: 'i-lucide-settings',
to: '/settings'
}
],
[
{
label: 'Appearance',
icon: 'i-lucide-sun-moon',
children: [
{
label: 'Light',
icon: 'i-lucide-sun',
type: 'checkbox',
checked: colorMode.value === 'light',
onUpdateChecked(checked: boolean) {
if (checked) {
colorMode.preference = 'light'
}
},
onSelect(e: Event) {
e.preventDefault()
}
},
{
label: 'Dark',
icon: 'i-lucide-moon',
type: 'checkbox',
checked: colorMode.value === 'dark',
onUpdateChecked(checked: boolean) {
if (checked) {
colorMode.preference = 'dark'
}
},
onSelect(e: Event) {
e.preventDefault()
}
}
]
}
],
[
{
label: 'GitHub',
icon: 'i-simple-icons-github',
to: 'https://github.com/nuxt/ui',
target: '_blank'
},
{
label: 'Log out',
icon: 'i-lucide-log-out'
}
]
])
defineShortcuts(extractShortcuts(teamsItems.value))
</script>
<template>
<div class="flex flex-1">
<USidebar
v-model:open="open"
collapsible="icon"
rail
:ui="{
container: 'h-full',
inner: 'bg-elevated/25 divide-transparent',
body: 'py-0'
}"
>
<template #header>
<UDropdownMenu
:items="teamsItems"
:content="{ align: 'start', collisionPadding: 12 }"
:ui="{ content: 'w-(--reka-dropdown-menu-trigger-width) min-w-48' }"
>
<UButton
v-bind="selectedTeam"
trailing-icon="i-lucide-chevrons-up-down"
color="neutral"
variant="ghost"
square
class="w-full data-[state=open]:bg-elevated overflow-hidden"
:ui="{
trailingIcon: 'text-dimmed ms-auto'
}"
/>
</UDropdownMenu>
</template>
<template #default="{ state }">
<UNavigationMenu
:key="state"
:items="getItems(state)"
orientation="vertical"
:ui="{ link: 'p-1.5 overflow-hidden' }"
/>
</template>
<template #footer>
<UDropdownMenu
:items="userItems"
:content="{ align: 'center', collisionPadding: 12 }"
:ui="{ content: 'w-(--reka-dropdown-menu-trigger-width) min-w-48' }"
>
<UButton
v-bind="user"
:label="user?.name"
trailing-icon="i-lucide-chevrons-up-down"
color="neutral"
variant="ghost"
square
class="w-full data-[state=open]:bg-elevated overflow-hidden"
:ui="{
trailingIcon: 'text-dimmed ms-auto'
}"
/>
</UDropdownMenu>
</template>
</USidebar>
<div class="flex-1 flex flex-col">
<div class="h-(--ui-header-height) shrink-0 flex items-center px-4 border-b border-default">
<UButton
icon="i-lucide-panel-left"
color="neutral"
variant="ghost"
aria-label="Toggle sidebar"
@click="open = !open"
/>
</div>
<div class="flex-1 p-4">
<Placeholder class="size-full" />
</div>
</div>
</div>
</template><script setup lang="ts">
import { ref, computed } from 'vue'
import { useColorMode } from '@vueuse/core'
import type { DropdownMenuItem, NavigationMenuItem } from '@nuxt/ui'
const open = ref(true)
const colorMode = useColorMode()
const teams = ref([
{
label: 'Nuxt',
avatar: {
src: 'https://github.com/nuxt.png',
alt: 'Nuxt'
}
},
{
label: 'Vue',
avatar: {
src: 'https://github.com/vuejs.png',
alt: 'Vue'
}
},
{
label: 'UnJS',
avatar: {
src: 'https://github.com/unjs.png',
alt: 'UnJS'
}
}
])
const selectedTeam = ref(teams.value[0])
const teamsItems = computed<DropdownMenuItem[][]>(() => {
return [
teams.value.map((team, index) => ({
...team,
kbds: ['meta', String(index + 1)],
onSelect() {
selectedTeam.value = team
}
})),
[
{
label: 'Create team',
icon: 'i-lucide-circle-plus'
}
]
]
})
function getItems(state: 'collapsed' | 'expanded') {
return [
{
label: 'Inbox',
icon: 'i-lucide-inbox',
badge: '4'
},
{
label: 'Issues',
icon: 'i-lucide-square-dot'
},
{
label: 'Activity',
icon: 'i-lucide-square-activity'
},
{
label: 'Settings',
icon: 'i-lucide-settings',
defaultOpen: true,
children:
state === 'expanded'
? [
{
label: 'General',
icon: 'i-lucide-house'
},
{
label: 'Team',
icon: 'i-lucide-users'
},
{
label: 'Billing',
icon: 'i-lucide-credit-card'
}
]
: []
}
] satisfies NavigationMenuItem[]
}
const user = ref({
name: 'Benjamin Canac',
avatar: {
src: 'https://github.com/benjamincanac.png',
alt: 'Benjamin Canac'
}
})
const userItems = computed<DropdownMenuItem[][]>(() => [
[
{
label: 'Profile',
icon: 'i-lucide-user'
},
{
label: 'Billing',
icon: 'i-lucide-credit-card'
},
{
label: 'Settings',
icon: 'i-lucide-settings',
to: '/settings'
}
],
[
{
label: 'Appearance',
icon: 'i-lucide-sun-moon',
children: [
{
label: 'Light',
icon: 'i-lucide-sun',
type: 'checkbox',
checked: colorMode.value === 'light',
onUpdateChecked(checked: boolean) {
if (checked) {
colorMode.preference = 'light'
}
},
onSelect(e: Event) {
e.preventDefault()
}
},
{
label: 'Dark',
icon: 'i-lucide-moon',
type: 'checkbox',
checked: colorMode.value === 'dark',
onUpdateChecked(checked: boolean) {
if (checked) {
colorMode.preference = 'dark'
}
},
onSelect(e: Event) {
e.preventDefault()
}
}
]
}
],
[
{
label: 'GitHub',
icon: 'i-simple-icons-github',
to: 'https://github.com/nuxt/ui',
target: '_blank'
},
{
label: 'Log out',
icon: 'i-lucide-log-out'
}
]
])
defineShortcuts(extractShortcuts(teamsItems.value))
</script>
<template>
<div class="flex flex-1">
<USidebar
v-model:open="open"
collapsible="icon"
rail
:ui="{
container: 'h-full',
inner: 'bg-elevated/25 divide-transparent',
body: 'py-0'
}"
>
<template #header>
<UDropdownMenu
:items="teamsItems"
:content="{ align: 'start', collisionPadding: 12 }"
:ui="{ content: 'w-(--reka-dropdown-menu-trigger-width) min-w-48' }"
>
<UButton
v-bind="selectedTeam"
trailing-icon="i-lucide-chevrons-up-down"
color="neutral"
variant="ghost"
square
class="w-full data-[state=open]:bg-elevated overflow-hidden"
:ui="{
trailingIcon: 'text-dimmed ms-auto'
}"
/>
</UDropdownMenu>
</template>
<template #default="{ state }">
<UNavigationMenu
:key="state"
:items="getItems(state)"
orientation="vertical"
:ui="{ link: 'p-1.5 overflow-hidden' }"
/>
</template>
<template #footer>
<UDropdownMenu
:items="userItems"
:content="{ align: 'center', collisionPadding: 12 }"
:ui="{ content: 'w-(--reka-dropdown-menu-trigger-width) min-w-48' }"
>
<UButton
v-bind="user"
:label="user?.name"
trailing-icon="i-lucide-chevrons-up-down"
color="neutral"
variant="ghost"
square
class="w-full data-[state=open]:bg-elevated overflow-hidden"
:ui="{
trailingIcon: 'text-dimmed ms-auto'
}"
/>
</UDropdownMenu>
</template>
</USidebar>
<div class="flex-1 flex flex-col">
<div class="h-(--ui-header-height) shrink-0 flex items-center px-4 border-b border-default">
<UButton
icon="i-lucide-panel-left"
color="neutral"
variant="ghost"
aria-label="Toggle sidebar"
@click="open = !open"
/>
</div>
<div class="flex-1 p-4">
<Placeholder class="size-full" />
</div>
</div>
</div>
</template>Variant équivalent
Utilisez la prop variant pour modifier le style visuel de la barre latérale. Par défaut sidebar.
<script setup lang="ts">
import type { NavigationMenuItem, SidebarProps } from '@nuxt/ui'
// Ignore the props for the example
defineProps<Pick<SidebarProps, 'variant' | 'collapsible' | 'side'>>()
const open = ref(true)
const items: NavigationMenuItem[] = [
{
label: 'Home',
icon: 'i-lucide-house',
active: true
},
{
label: 'Inbox',
icon: 'i-lucide-inbox',
badge: '4'
},
{
label: 'Contacts',
icon: 'i-lucide-users'
}
]
</script>
<template>
<div
class="flex flex-1"
:class="[
variant === 'inset' && 'bg-neutral-50 dark:bg-neutral-950',
side === 'right' && 'flex-row-reverse'
]"
>
<USidebar
v-model:open="open"
:variant="variant"
:collapsible="collapsible"
:side="side"
:ui="{
container: 'h-full'
}"
>
<template #header>
<UIcon name="i-logos-nuxt-icon" class="size-8" />
</template>
<UNavigationMenu
:items="items"
orientation="vertical"
:ui="{ link: 'p-1.5 overflow-hidden' }"
/>
</USidebar>
<div
class="flex-1 flex flex-col overflow-hidden lg:peer-data-[variant=floating]:my-4 peer-data-[variant=inset]:m-4 lg:peer-data-[variant=inset]:not-peer-data-[collapsible=offcanvas]:ms-0 peer-data-[variant=inset]:rounded-xl peer-data-[variant=inset]:shadow-sm peer-data-[variant=inset]:ring peer-data-[variant=inset]:ring-default bg-default"
>
<div
class="h-(--ui-header-height) shrink-0 flex items-center px-4"
:class="[
variant !== 'floating' && 'border-b border-default',
side === 'right' && 'justify-end'
]"
>
<UButton
:icon="side === 'left' ? 'i-lucide-panel-left' : 'i-lucide-panel-right'"
color="neutral"
variant="ghost"
aria-label="Toggle sidebar"
@click="open = !open"
/>
</div>
<div class="flex-1 p-4">
<Placeholder class="size-full" />
</div>
</div>
</div>
</template><script setup lang="ts">
import { ref } from 'vue'
import type { NavigationMenuItem, SidebarProps } from '@nuxt/ui'
// Ignore the props for the example
defineProps<Pick<SidebarProps, 'variant' | 'collapsible' | 'side'>>()
const open = ref(true)
const items: NavigationMenuItem[] = [
{
label: 'Home',
icon: 'i-lucide-house',
active: true
},
{
label: 'Inbox',
icon: 'i-lucide-inbox',
badge: '4'
},
{
label: 'Contacts',
icon: 'i-lucide-users'
}
]
</script>
<template>
<div
class="flex flex-1"
:class="[
variant === 'inset' && 'bg-neutral-50 dark:bg-neutral-950',
side === 'right' && 'flex-row-reverse'
]"
>
<USidebar
v-model:open="open"
:variant="variant"
:collapsible="collapsible"
:side="side"
:ui="{
container: 'h-full'
}"
>
<template #header>
<UIcon name="i-logos-nuxt-icon" class="size-8" />
</template>
<UNavigationMenu
:items="items"
orientation="vertical"
:ui="{ link: 'p-1.5 overflow-hidden' }"
/>
</USidebar>
<div
class="flex-1 flex flex-col overflow-hidden lg:peer-data-[variant=floating]:my-4 peer-data-[variant=inset]:m-4 lg:peer-data-[variant=inset]:not-peer-data-[collapsible=offcanvas]:ms-0 peer-data-[variant=inset]:rounded-xl peer-data-[variant=inset]:shadow-sm peer-data-[variant=inset]:ring peer-data-[variant=inset]:ring-default bg-default"
>
<div
class="h-(--ui-header-height) shrink-0 flex items-center px-4"
:class="[
variant !== 'floating' && 'border-b border-default',
side === 'right' && 'justify-end'
]"
>
<UButton
:icon="side === 'left' ? 'i-lucide-panel-left' : 'i-lucide-panel-right'"
color="neutral"
variant="ghost"
aria-label="Toggle sidebar"
@click="open = !open"
/>
</div>
<div class="flex-1 p-4">
<Placeholder class="size-full" />
</div>
</div>
</div>
</template>Rétractable
Utilisez la prop collapsible pour modifier le comportement d'effondrement de la barre latérale. Par défaut, offcanvas.
offcanvas: La barre latérale glisse complètement hors de vue.icon: La barre latérale se réduit à une largeur d'icône seulement.none: La barre latérale n'est pas pliable.
<script setup lang="ts">
import type { NavigationMenuItem, SidebarProps } from '@nuxt/ui'
// Ignore the props for the example
defineProps<Pick<SidebarProps, 'variant' | 'collapsible' | 'side'>>()
const open = ref(true)
const items: NavigationMenuItem[] = [
{
label: 'Home',
icon: 'i-lucide-house',
active: true
},
{
label: 'Inbox',
icon: 'i-lucide-inbox',
badge: '4'
},
{
label: 'Contacts',
icon: 'i-lucide-users'
}
]
</script>
<template>
<div
class="flex flex-1"
:class="[
variant === 'inset' && 'bg-neutral-50 dark:bg-neutral-950',
side === 'right' && 'flex-row-reverse'
]"
>
<USidebar
v-model:open="open"
:variant="variant"
:collapsible="collapsible"
:side="side"
:ui="{
container: 'h-full'
}"
>
<template #header>
<UIcon name="i-logos-nuxt-icon" class="size-8" />
</template>
<UNavigationMenu
:items="items"
orientation="vertical"
:ui="{ link: 'p-1.5 overflow-hidden' }"
/>
</USidebar>
<div
class="flex-1 flex flex-col overflow-hidden lg:peer-data-[variant=floating]:my-4 peer-data-[variant=inset]:m-4 lg:peer-data-[variant=inset]:not-peer-data-[collapsible=offcanvas]:ms-0 peer-data-[variant=inset]:rounded-xl peer-data-[variant=inset]:shadow-sm peer-data-[variant=inset]:ring peer-data-[variant=inset]:ring-default bg-default"
>
<div
class="h-(--ui-header-height) shrink-0 flex items-center px-4"
:class="[
variant !== 'floating' && 'border-b border-default',
side === 'right' && 'justify-end'
]"
>
<UButton
:icon="side === 'left' ? 'i-lucide-panel-left' : 'i-lucide-panel-right'"
color="neutral"
variant="ghost"
aria-label="Toggle sidebar"
@click="open = !open"
/>
</div>
<div class="flex-1 p-4">
<Placeholder class="size-full" />
</div>
</div>
</div>
</template><script setup lang="ts">
import { ref } from 'vue'
import type { NavigationMenuItem, SidebarProps } from '@nuxt/ui'
// Ignore the props for the example
defineProps<Pick<SidebarProps, 'variant' | 'collapsible' | 'side'>>()
const open = ref(true)
const items: NavigationMenuItem[] = [
{
label: 'Home',
icon: 'i-lucide-house',
active: true
},
{
label: 'Inbox',
icon: 'i-lucide-inbox',
badge: '4'
},
{
label: 'Contacts',
icon: 'i-lucide-users'
}
]
</script>
<template>
<div
class="flex flex-1"
:class="[
variant === 'inset' && 'bg-neutral-50 dark:bg-neutral-950',
side === 'right' && 'flex-row-reverse'
]"
>
<USidebar
v-model:open="open"
:variant="variant"
:collapsible="collapsible"
:side="side"
:ui="{
container: 'h-full'
}"
>
<template #header>
<UIcon name="i-logos-nuxt-icon" class="size-8" />
</template>
<UNavigationMenu
:items="items"
orientation="vertical"
:ui="{ link: 'p-1.5 overflow-hidden' }"
/>
</USidebar>
<div
class="flex-1 flex flex-col overflow-hidden lg:peer-data-[variant=floating]:my-4 peer-data-[variant=inset]:m-4 lg:peer-data-[variant=inset]:not-peer-data-[collapsible=offcanvas]:ms-0 peer-data-[variant=inset]:rounded-xl peer-data-[variant=inset]:shadow-sm peer-data-[variant=inset]:ring peer-data-[variant=inset]:ring-default bg-default"
>
<div
class="h-(--ui-header-height) shrink-0 flex items-center px-4"
:class="[
variant !== 'floating' && 'border-b border-default',
side === 'right' && 'justify-end'
]"
>
<UButton
:icon="side === 'left' ? 'i-lucide-panel-left' : 'i-lucide-panel-right'"
color="neutral"
variant="ghost"
aria-label="Toggle sidebar"
@click="open = !open"
/>
</div>
<div class="flex-1 p-4">
<Placeholder class="size-full" />
</div>
</div>
</div>
</template>state dans les accessoires de la fente pour personnaliser le contenu de la barre latérale lorsqu 'elle est réduite.Side
Utilisez la prop side pour changer le côté de la barre latérale. Par défaut à left.
<script setup lang="ts">
import type { NavigationMenuItem, SidebarProps } from '@nuxt/ui'
// Ignore the props for the example
defineProps<Pick<SidebarProps, 'variant' | 'collapsible' | 'side'>>()
const open = ref(true)
const items: NavigationMenuItem[] = [
{
label: 'Home',
icon: 'i-lucide-house',
active: true
},
{
label: 'Inbox',
icon: 'i-lucide-inbox',
badge: '4'
},
{
label: 'Contacts',
icon: 'i-lucide-users'
}
]
</script>
<template>
<div
class="flex flex-1"
:class="[
variant === 'inset' && 'bg-neutral-50 dark:bg-neutral-950',
side === 'right' && 'flex-row-reverse'
]"
>
<USidebar
v-model:open="open"
:variant="variant"
:collapsible="collapsible"
:side="side"
:ui="{
container: 'h-full'
}"
>
<template #header>
<UIcon name="i-logos-nuxt-icon" class="size-8" />
</template>
<UNavigationMenu
:items="items"
orientation="vertical"
:ui="{ link: 'p-1.5 overflow-hidden' }"
/>
</USidebar>
<div
class="flex-1 flex flex-col overflow-hidden lg:peer-data-[variant=floating]:my-4 peer-data-[variant=inset]:m-4 lg:peer-data-[variant=inset]:not-peer-data-[collapsible=offcanvas]:ms-0 peer-data-[variant=inset]:rounded-xl peer-data-[variant=inset]:shadow-sm peer-data-[variant=inset]:ring peer-data-[variant=inset]:ring-default bg-default"
>
<div
class="h-(--ui-header-height) shrink-0 flex items-center px-4"
:class="[
variant !== 'floating' && 'border-b border-default',
side === 'right' && 'justify-end'
]"
>
<UButton
:icon="side === 'left' ? 'i-lucide-panel-left' : 'i-lucide-panel-right'"
color="neutral"
variant="ghost"
aria-label="Toggle sidebar"
@click="open = !open"
/>
</div>
<div class="flex-1 p-4">
<Placeholder class="size-full" />
</div>
</div>
</div>
</template><script setup lang="ts">
import { ref } from 'vue'
import type { NavigationMenuItem, SidebarProps } from '@nuxt/ui'
// Ignore the props for the example
defineProps<Pick<SidebarProps, 'variant' | 'collapsible' | 'side'>>()
const open = ref(true)
const items: NavigationMenuItem[] = [
{
label: 'Home',
icon: 'i-lucide-house',
active: true
},
{
label: 'Inbox',
icon: 'i-lucide-inbox',
badge: '4'
},
{
label: 'Contacts',
icon: 'i-lucide-users'
}
]
</script>
<template>
<div
class="flex flex-1"
:class="[
variant === 'inset' && 'bg-neutral-50 dark:bg-neutral-950',
side === 'right' && 'flex-row-reverse'
]"
>
<USidebar
v-model:open="open"
:variant="variant"
:collapsible="collapsible"
:side="side"
:ui="{
container: 'h-full'
}"
>
<template #header>
<UIcon name="i-logos-nuxt-icon" class="size-8" />
</template>
<UNavigationMenu
:items="items"
orientation="vertical"
:ui="{ link: 'p-1.5 overflow-hidden' }"
/>
</USidebar>
<div
class="flex-1 flex flex-col overflow-hidden lg:peer-data-[variant=floating]:my-4 peer-data-[variant=inset]:m-4 lg:peer-data-[variant=inset]:not-peer-data-[collapsible=offcanvas]:ms-0 peer-data-[variant=inset]:rounded-xl peer-data-[variant=inset]:shadow-sm peer-data-[variant=inset]:ring peer-data-[variant=inset]:ring-default bg-default"
>
<div
class="h-(--ui-header-height) shrink-0 flex items-center px-4"
:class="[
variant !== 'floating' && 'border-b border-default',
side === 'right' && 'justify-end'
]"
>
<UButton
:icon="side === 'left' ? 'i-lucide-panel-left' : 'i-lucide-panel-right'"
color="neutral"
variant="ghost"
aria-label="Toggle sidebar"
@click="open = !open"
/>
</div>
<div class="flex-1 p-4">
<Placeholder class="size-full" />
</div>
</div>
</div>
</template>Titre
Utilisez la prop title pour définir le titre de l'en-tête de la barre latérale.
<template>
<USidebar title="Navigation">
<Placeholder class="h-full" />
</USidebar>
</template>Description
Utilisez la prop description pour définir la description de l'en-tête de la barre latérale.
<template>
<USidebar title="Navigation" description="Browse your workspace">
<Placeholder class="h-full" />
</USidebar>
</template>rail électrique
Utilisez le prop rail pour afficher un mince bord interactif sur la barre latérale qui permet de basculer l'état effondré au clic. Le rail n'est rendu que lorsque collapsible n'est pas none.
<template>
<USidebar rail collapsible="icon" title="Navigation">
<Placeholder class="h-full" />
</USidebar>
</template>Fermer
Utilisez la prop close pour afficher un bouton de fermeture dans l'en-tête de la barre latérale. Le bouton de fermeture n'est rendu que lorsque collapsible n'est pas none.
Vous pouvez passer n'importe quelle propriété du composant Button pour le personnaliser.
<template>
<USidebar close rail collapsible="icon" title="Navigation">
<Placeholder class="h-full" />
</USidebar>
</template>Fermer l'icône
Utilisez la prop close-icon pour personnaliser le bouton de fermeture Icon.
<template>
<USidebar
close
close-icon="i-lucide-panel-right-close"
rail
collapsible="icon"
side="right"
title="Navigation"
>
<Placeholder class="h-full" />
</USidebar>
</template>app.config.ts sous la touche ui.icons.close.vite.config.ts sous la touche ui.icons.close.mode
Utilisez la prop mode pour changer le mode du menu de la barre latérale sur mobile. Par défaut, slideover.
<script setup lang="ts">
import type { NavigationMenuItem, SidebarProps } from '@nuxt/ui'
// Ignore the props for the example
defineProps<Pick<SidebarProps, 'mode'>>()
const open = ref(true)
const items: NavigationMenuItem[] = [{
label: 'Home',
icon: 'i-lucide-house',
active: true
}, {
label: 'Inbox',
icon: 'i-lucide-inbox',
badge: '4'
}, {
label: 'Contacts',
icon: 'i-lucide-users'
}]
</script>
<template>
<div class="flex flex-1">
<USidebar v-model:open="open" :mode="mode" title="Navigation">
<UNavigationMenu
:items="items"
orientation="vertical"
:ui="{ link: 'p-1.5 overflow-hidden' }"
/>
</USidebar>
<div class="flex-1 flex flex-col">
<div class="h-(--ui-header-height) shrink-0 flex items-center px-4 border-b border-default">
<UButton
icon="i-lucide-panel-left"
color="neutral"
variant="ghost"
aria-label="Toggle sidebar"
@click="open = !open"
/>
</div>
<div class="flex-1 p-4">
<Placeholder class="size-full" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { NavigationMenuItem, SidebarProps } from '@nuxt/ui'
// Ignore the props for the example
defineProps<Pick<SidebarProps, 'mode'>>()
const open = ref(true)
const items: NavigationMenuItem[] = [{
label: 'Home',
icon: 'i-lucide-house',
active: true
}, {
label: 'Inbox',
icon: 'i-lucide-inbox',
badge: '4'
}, {
label: 'Contacts',
icon: 'i-lucide-users'
}]
</script>
<template>
<div class="flex flex-1">
<USidebar v-model:open="open" :mode="mode" title="Navigation">
<UNavigationMenu
:items="items"
orientation="vertical"
:ui="{ link: 'p-1.5 overflow-hidden' }"
/>
</USidebar>
<div class="flex-1 flex flex-col">
<div class="h-(--ui-header-height) shrink-0 flex items-center px-4 border-b border-default">
<UButton
icon="i-lucide-panel-left"
color="neutral"
variant="ghost"
aria-label="Toggle sidebar"
@click="open = !open"
/>
</div>
<div class="flex-1 p-4">
<Placeholder class="size-full" />
</div>
</div>
</div>
</template>
menu pour personnaliser le menu de la barre latérale, il s'adaptera en fonction du mode que vous choisissez.Exemples
Control état ouvert
Vous pouvez contrôler l'état ouvert à l'aide de la prop open ou de la directive v-model:open. Sur le bureau, il contrôle l'état développé/réduit, sur mobile, il ouvre/ferme le menu de la feuille.
<script setup lang="ts">
import type { NavigationMenuItem } from '@nuxt/ui'
const open = ref(true)
defineShortcuts({
o: () => (open.value = !open.value)
})
const items: NavigationMenuItem[] = [
{
label: 'Home',
icon: 'i-lucide-house',
active: true
},
{
label: 'Inbox',
icon: 'i-lucide-inbox',
badge: '4'
},
{
label: 'Contacts',
icon: 'i-lucide-users'
}
]
</script>
<template>
<div class="flex flex-1">
<USidebar v-model:open="open" title="Navigation" collapsible="icon">
<UNavigationMenu
:items="items"
orientation="vertical"
:ui="{ link: 'p-1.5 overflow-hidden' }"
/>
</USidebar>
<div class="flex-1 flex flex-col">
<div class="h-(--ui-header-height) shrink-0 flex items-center px-4 border-b border-default">
<UButton
icon="i-lucide-panel-left"
color="neutral"
variant="ghost"
:aria-label="open ? 'Close sidebar' : 'Open sidebar'"
@click="open = !open"
/>
</div>
<div class="flex-1 p-4">
<Placeholder class="size-full" />
</div>
</div>
</div>
</template><script setup lang="ts">
import { ref } from 'vue'
import type { NavigationMenuItem } from '@nuxt/ui'
const open = ref(true)
defineShortcuts({
o: () => (open.value = !open.value)
})
const items: NavigationMenuItem[] = [
{
label: 'Home',
icon: 'i-lucide-house',
active: true
},
{
label: 'Inbox',
icon: 'i-lucide-inbox',
badge: '4'
},
{
label: 'Contacts',
icon: 'i-lucide-users'
}
]
</script>
<template>
<div class="flex flex-1">
<USidebar v-model:open="open" title="Navigation" collapsible="icon">
<UNavigationMenu
:items="items"
orientation="vertical"
:ui="{ link: 'p-1.5 overflow-hidden' }"
/>
</USidebar>
<div class="flex-1 flex flex-col">
<div class="h-(--ui-header-height) shrink-0 flex items-center px-4 border-b border-default">
<UButton
icon="i-lucide-panel-left"
color="neutral"
variant="ghost"
:aria-label="open ? 'Close sidebar' : 'Open sidebar'"
@click="open = !open"
/>
</div>
<div class="flex-1 p-4">
<Placeholder class="size-full" />
</div>
</div>
</div>
</template>defineShortcuts, vous pouvez basculer l'état ouvert de la barre latérale en appuyant sur: kbd{value="O"}.Persist état ouvert
Utilisez useLocalStorage à partir de VueUse ou useCookie au lieu de ref pour conserver l'état de la barre latérale à travers les rechargements de page.
<script setup lang="ts">
import type { NavigationMenuItem } from '@nuxt/ui'
const open = useLocalStorage('sidebar-open', true)
defineShortcuts({
o: () => (open.value = !open.value)
})
const items: NavigationMenuItem[] = [
{
label: 'Home',
icon: 'i-lucide-house',
active: true
},
{
label: 'Inbox',
icon: 'i-lucide-inbox',
badge: '4'
},
{
label: 'Contacts',
icon: 'i-lucide-users'
}
]
</script>
<template>
<div class="flex flex-1">
<USidebar v-model:open="open" title="Navigation" collapsible="icon">
<UNavigationMenu
:items="items"
orientation="vertical"
:ui="{ link: 'p-1.5 overflow-hidden' }"
/>
</USidebar>
<div class="flex-1 flex flex-col">
<div class="h-(--ui-header-height) shrink-0 flex items-center px-4 border-b border-default">
<UButton
icon="i-lucide-panel-left"
color="neutral"
variant="ghost"
aria-label="Toggle sidebar"
@click="open = !open"
/>
</div>
<div class="flex-1 p-4">
<Placeholder class="size-full" />
</div>
</div>
</div>
</template><script setup lang="ts">
import { useLocalStorage } from '@vueuse/core'
import type { NavigationMenuItem } from '@nuxt/ui'
const open = useLocalStorage('sidebar-open', true)
defineShortcuts({
o: () => (open.value = !open.value)
})
const items: NavigationMenuItem[] = [
{
label: 'Home',
icon: 'i-lucide-house',
active: true
},
{
label: 'Inbox',
icon: 'i-lucide-inbox',
badge: '4'
},
{
label: 'Contacts',
icon: 'i-lucide-users'
}
]
</script>
<template>
<div class="flex flex-1">
<USidebar v-model:open="open" title="Navigation" collapsible="icon">
<UNavigationMenu
:items="items"
orientation="vertical"
:ui="{ link: 'p-1.5 overflow-hidden' }"
/>
</USidebar>
<div class="flex-1 flex flex-col">
<div class="h-(--ui-header-height) shrink-0 flex items-center px-4 border-b border-default">
<UButton
icon="i-lucide-panel-left"
color="neutral"
variant="ghost"
aria-label="Toggle sidebar"
@click="open = !open"
/>
</div>
<div class="flex-1 p-4">
<Placeholder class="size-full" />
</div>
</div>
</div>
</template>ref(true) par useLocalStorage('sidebar-open', true).Avec largeur personnalisée
La largeur de la barre latérale est contrôlée par la variable CSS --sidebar-width (par défaut 16rem). La largeur de l'icône réduite est contrôlée par --sidebar-width-icon (par défaut 4rem).
Remplacez-les globalement dans votre CSS ou par instance avec l'attribut style.
<script setup lang="ts">
import type { NavigationMenuItem } from '@nuxt/ui'
const open = ref(true)
const items: NavigationMenuItem[] = [
{
label: 'Home',
icon: 'i-lucide-house',
active: true
},
{
label: 'Inbox',
icon: 'i-lucide-inbox',
badge: '4'
},
{
label: 'Contacts',
icon: 'i-lucide-users'
}
]
</script>
<template>
<div class="flex flex-1">
<USidebar v-model:open="open" collapsible="icon" :style="{ '--sidebar-width': '20rem' }">
<UNavigationMenu
:items="items"
orientation="vertical"
:ui="{ link: 'p-1.5 overflow-hidden' }"
/>
</USidebar>
<div class="flex-1 flex flex-col">
<div class="h-(--ui-header-height) shrink-0 flex items-center px-4 border-b border-default">
<UButton
icon="i-lucide-panel-left"
color="neutral"
variant="ghost"
aria-label="Toggle sidebar"
@click="open = !open"
/>
</div>
<div class="flex-1 p-4">
<Placeholder class="size-full" />
</div>
</div>
</div>
</template><script setup lang="ts">
import { ref } from 'vue'
import type { NavigationMenuItem } from '@nuxt/ui'
const open = ref(true)
const items: NavigationMenuItem[] = [
{
label: 'Home',
icon: 'i-lucide-house',
active: true
},
{
label: 'Inbox',
icon: 'i-lucide-inbox',
badge: '4'
},
{
label: 'Contacts',
icon: 'i-lucide-users'
}
]
</script>
<template>
<div class="flex flex-1">
<USidebar v-model:open="open" collapsible="icon" :style="{ '--sidebar-width': '20rem' }">
<UNavigationMenu
:items="items"
orientation="vertical"
:ui="{ link: 'p-1.5 overflow-hidden' }"
/>
</USidebar>
<div class="flex-1 flex flex-col">
<div class="h-(--ui-header-height) shrink-0 flex items-center px-4 border-b border-default">
<UButton
icon="i-lucide-panel-left"
color="neutral"
variant="ghost"
aria-label="Toggle sidebar"
@click="open = !open"
/>
</div>
<div class="flex-1 p-4">
<Placeholder class="size-full" />
</div>
</div>
</div>
</template>Avec header
Pour positionner la barre latérale sous un Header, personnalisez le gap et le container à l'aide de la prop ui.
<script setup lang="ts">
import type { NavigationMenuItem } from '@nuxt/ui'
const open = ref(true)
const items: NavigationMenuItem[] = [
{
label: 'Home',
icon: 'i-lucide-house',
active: true
},
{
label: 'Inbox',
icon: 'i-lucide-inbox',
badge: '4'
},
{
label: 'Contacts',
icon: 'i-lucide-users'
}
]
</script>
<template>
<div class="flex flex-col flex-1">
<UHeader toggle-side="left" :ui="{ container: 'px-4!' }">
<template #toggle>
<UButton
icon="i-lucide-panel-left"
color="neutral"
variant="ghost"
aria-label="Toggle sidebar"
@click="open = !open"
/>
</template>
</UHeader>
<div class="flex flex-1 min-h-0">
<USidebar
v-model:open="open"
collapsible="icon"
:ui="{
gap: 'h-[calc(100%-var(--ui-header-height))]',
container:
'absolute top-(--ui-header-height) bottom-0 h-[calc(100%-var(--ui-header-height))]'
}"
>
<UNavigationMenu
:items="items"
orientation="vertical"
:ui="{ link: 'p-1.5 overflow-hidden' }"
/>
</USidebar>
<div class="flex-1 p-4">
<Placeholder class="size-full" />
</div>
</div>
</div>
</template><script setup lang="ts">
import { ref } from 'vue'
import type { NavigationMenuItem } from '@nuxt/ui'
const open = ref(true)
const items: NavigationMenuItem[] = [
{
label: 'Home',
icon: 'i-lucide-house',
active: true
},
{
label: 'Inbox',
icon: 'i-lucide-inbox',
badge: '4'
},
{
label: 'Contacts',
icon: 'i-lucide-users'
}
]
</script>
<template>
<div class="flex flex-col flex-1">
<UHeader toggle-side="left" :ui="{ container: 'px-4!' }">
<template #toggle>
<UButton
icon="i-lucide-panel-left"
color="neutral"
variant="ghost"
aria-label="Toggle sidebar"
@click="open = !open"
/>
</template>
</UHeader>
<div class="flex flex-1 min-h-0">
<USidebar
v-model:open="open"
collapsible="icon"
:ui="{
gap: 'h-[calc(100%-var(--ui-header-height))]',
container:
'absolute top-(--ui-header-height) bottom-0 h-[calc(100%-var(--ui-header-height))]'
}"
>
<UNavigationMenu
:items="items"
orientation="vertical"
:ui="{ link: 'p-1.5 overflow-hidden' }"
/>
</USidebar>
<div class="flex-1 p-4">
<Placeholder class="size-full" />
</div>
</div>
</div>
</template>--ui-header-height est par défaut 4rem et est utilisée par l'en-tête. Ajustez-la si votre barre de navigation utilise une hauteur différente.Avec chat AI
Utilisez la barre latérale sur le côté droit avec ChatMessages et ChatPrompt pour créer un panneau de discussion AI.
<script setup lang="ts">
import type { UIMessage } from 'ai'
import { isTextUIPart } from 'ai'
import { useChat } from '@ai-sdk/vue'
import { isPartStreaming } from '@nuxt/ui/utils/ai'
import { Markdown } from '@comark/vue'
import shiki from '@comark/vue/plugins/shiki'
import security from '@comark/vue/plugins/security'
const plugins = [
shiki(),
security({ blockedTags: ['script', 'style', 'iframe', 'object', 'embed', 'form'] })
]
const open = ref(true)
const input = ref('')
const initialMessages: UIMessage[] = [
{
id: '1',
role: 'user',
parts: [{ type: 'text', text: 'What is Nuxt UI?' }]
},
{
id: '2',
role: 'assistant',
parts: [
{
type: 'text',
text: 'Nuxt UI is a Vue component library built on Reka UI, Tailwind CSS, and Tailwind Variants. It provides 125+ accessible components for building modern web apps.'
}
]
}
]
const { messages, status, error, sendMessage, regenerate, stop } = useChat({
messages: initialMessages
})
function onSubmit() {
if (!input.value.trim()) return
sendMessage({ text: input.value })
input.value = ''
}
const ui = {
prose: {
p: { base: 'my-2 text-sm/6' },
li: { base: 'my-0.5 text-sm/6' },
ul: { base: 'my-2' },
ol: { base: 'my-2' },
h1: { base: 'text-xl mb-4' },
h2: { base: 'text-lg mt-6 mb-3' },
h3: { base: 'text-base mt-4 mb-2' },
h4: { base: 'text-sm mt-3 mb-1.5' },
code: { base: 'text-xs' },
pre: { root: 'my-2', base: 'text-xs/5' },
table: { root: 'my-2' },
hr: { base: 'my-4' }
}
}
</script>
<template>
<div class="flex flex-1">
<div class="flex-1 flex flex-col">
<div
class="h-(--ui-header-height) shrink-0 flex items-center justify-end px-4 border-b border-default"
>
<UButton
icon="i-lucide-panel-right"
color="neutral"
variant="ghost"
aria-label="Toggle sidebar"
@click="open = !open"
/>
</div>
<div class="flex-1 p-4">
<Placeholder class="size-full" />
</div>
</div>
<USidebar
v-model:open="open"
side="right"
title="AI Chat"
close
:style="{ '--sidebar-width': '20rem' }"
:ui="{ container: 'h-full' }"
>
<UTheme :ui="ui">
<UChatMessages :messages="messages" :status="status" compact class="px-0">
<template #content="{ message }">
<template
v-for="(part, index) in message.parts"
:key="`${message.id}-${part.type}-${index}`"
>
<template v-if="isTextUIPart(part)">
<Markdown
v-if="message.role === 'assistant'"
:value="part.text"
:streaming="isPartStreaming(part)"
:plugins="plugins"
class="*:first:mt-0 *:last:mb-0"
/>
<p v-else-if="message.role === 'user'" class="whitespace-pre-wrap text-sm/6">
{{ part.text }}
</p>
</template>
</template>
</template>
</UChatMessages>
</UTheme>
<template #footer>
<UChatPrompt
v-model="input"
:error="error"
:autofocus="false"
variant="subtle"
size="sm"
@submit="onSubmit"
>
<UChatPromptSubmit size="sm" :status="status" @stop="stop()" @reload="regenerate()" />
</UChatPrompt>
</template>
</USidebar>
</div>
</template><script setup lang="ts">
import { ref } from 'vue'
import type { UIMessage } from 'ai'
import { isTextUIPart } from 'ai'
import { useChat } from '@ai-sdk/vue'
import { isPartStreaming } from '@nuxt/ui/utils/ai'
import { Markdown } from '@comark/vue'
import shiki from '@comark/vue/plugins/shiki'
import security from '@comark/vue/plugins/security'
const plugins = [
shiki(),
security({ blockedTags: ['script', 'style', 'iframe', 'object', 'embed', 'form'] })
]
const open = ref(true)
const input = ref('')
const initialMessages: UIMessage[] = [
{
id: '1',
role: 'user',
parts: [{ type: 'text', text: 'What is Nuxt UI?' }]
},
{
id: '2',
role: 'assistant',
parts: [
{
type: 'text',
text: 'Nuxt UI is a Vue component library built on Reka UI, Tailwind CSS, and Tailwind Variants. It provides 125+ accessible components for building modern web apps.'
}
]
}
]
const { messages, status, error, sendMessage, regenerate, stop } = useChat({
messages: initialMessages
})
function onSubmit() {
if (!input.value.trim()) return
sendMessage({ text: input.value })
input.value = ''
}
const ui = {
prose: {
p: { base: 'my-2 text-sm/6' },
li: { base: 'my-0.5 text-sm/6' },
ul: { base: 'my-2' },
ol: { base: 'my-2' },
h1: { base: 'text-xl mb-4' },
h2: { base: 'text-lg mt-6 mb-3' },
h3: { base: 'text-base mt-4 mb-2' },
h4: { base: 'text-sm mt-3 mb-1.5' },
code: { base: 'text-xs' },
pre: { root: 'my-2', base: 'text-xs/5' },
table: { root: 'my-2' },
hr: { base: 'my-4' }
}
}
</script>
<template>
<div class="flex flex-1">
<div class="flex-1 flex flex-col">
<div
class="h-(--ui-header-height) shrink-0 flex items-center justify-end px-4 border-b border-default"
>
<UButton
icon="i-lucide-panel-right"
color="neutral"
variant="ghost"
aria-label="Toggle sidebar"
@click="open = !open"
/>
</div>
<div class="flex-1 p-4">
<Placeholder class="size-full" />
</div>
</div>
<USidebar
v-model:open="open"
side="right"
title="AI Chat"
close
:style="{ '--sidebar-width': '20rem' }"
:ui="{ container: 'h-full' }"
>
<UTheme :ui="ui">
<UChatMessages :messages="messages" :status="status" compact class="px-0">
<template #content="{ message }">
<template
v-for="(part, index) in message.parts"
:key="`${message.id}-${part.type}-${index}`"
>
<template v-if="isTextUIPart(part)">
<Markdown
v-if="message.role === 'assistant'"
:value="part.text"
:streaming="isPartStreaming(part)"
:plugins="plugins"
class="*:first:mt-0 *:last:mb-0"
/>
<p v-else-if="message.role === 'user'" class="whitespace-pre-wrap text-sm/6">
{{ part.text }}
</p>
</template>
</template>
</template>
</UChatMessages>
</UTheme>
<template #footer>
<UChatPrompt
v-model="input"
:error="error"
:autofocus="false"
variant="subtle"
size="sm"
@submit="onSubmit"
>
<UChatPromptSubmit size="sm" :status="status" @stop="stop()" @reload="regenerate()" />
</UChatPrompt>
</template>
</USidebar>
</div>
</template>API
Props équipements
| Prop | Default | Type |
|---|---|---|
as | 'aside' | anyThe element or component this component should render as. |
variant | 'sidebar' | "floating" | "sidebar" | "inset"The visual variant of the sidebar. |
collapsible | 'offcanvas' | "offcanvas" | "icon" | "none"The collapse behavior of the sidebar.
|
side | 'left' | "left" | "right"The side to render the sidebar on. |
title | stringThe title displayed in the sidebar header. | |
description | stringThe description displayed in the sidebar header. | |
close | false | boolean | Omit<ButtonProps, LinkPropsKeys> Display a close button to collapse the sidebar.
Only renders when |
closeIcon | appConfig.ui.icons.close | anyThe icon displayed in the close button. |
rail | false | boolean Display a rail on the sidebar edge to toggle collapse.
Only renders when |
transition | true | boolean Animate the sidebar when collapsing or expanding. |
mode | 'slideover' | TThe mode of the sidebar menu on mobile. |
menu | SidebarMenu<T>The props for the sidebar menu component on mobile. | |
open | true | boolean |
ui | { root?: SlotClass; gap?: SlotClass; container?: SlotClass; inner?: SlotClass; header?: SlotClass; wrapper?: SlotClass; title?: SlotClass; description?: SlotClass; actions?: SlotClass; close?: SlotClass; body?: SlotClass; footer?: SlotClass; rail?: SlotClass; } |
Slots
| Slot | Type |
|---|---|
header | { state: SidebarState; open: boolean; close: () => void; } |
title | { state: SidebarState; } |
description | { state: SidebarState; } |
actions | { state: SidebarState; } |
close | { ui: object; } |
default | { state: SidebarState; open: boolean; close: () => void; } |
footer | { state: SidebarState; open: boolean; close: () => void; } |
rail | { ui: object; } |
content | { close: () => void; } |
Thème
export default defineAppConfig({
ui: {
sidebar: {
slots: {
root: 'peer [--sidebar-width:16rem] [--sidebar-width-icon:4rem]',
gap: 'relative w-(--sidebar-width) bg-transparent',
container: 'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) lg:flex',
inner: 'flex size-full flex-col overflow-hidden divide-y divide-default',
header: 'flex items-center gap-1.5 overflow-hidden px-4 min-h-(--ui-header-height)',
wrapper: 'min-w-0 flex-1',
title: 'text-highlighted font-semibold truncate',
description: 'text-muted text-sm truncate',
actions: 'flex items-center gap-1.5 shrink-0',
close: '',
body: 'flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto p-4',
footer: 'flex items-center gap-1.5 overflow-hidden p-4',
rail: [
'absolute inset-y-0 z-20 hidden w-4 after:absolute after:inset-y-0 after:left-1/2 after:w-px lg:flex hover:after:bg-(--ui-border-accented)',
'after:transition-colors'
]
},
variants: {
transition: {
true: {
gap: 'transition-[width] duration-200 ease-out motion-reduce:transition-none',
container: 'transition-[inset-inline-start,inset-inline-end,width] duration-200 ease-out motion-reduce:transition-none'
}
},
side: {
left: {
container: 'start-0 border-e border-default',
rail: 'end-0 translate-x-1/2 rtl:-translate-x-1/2'
},
right: {
container: 'end-0 border-s border-default',
rail: '-start-px -translate-x-1/2 rtl:translate-x-1/2'
}
},
collapsible: {
offcanvas: {
root: 'group/sidebar hidden lg:block',
gap: 'data-[state=collapsed]:w-0'
},
icon: {
root: 'group/sidebar hidden lg:block',
gap: 'data-[state=collapsed]:w-(--sidebar-width-icon)',
container: 'data-[state=collapsed]:w-(--sidebar-width-icon)',
actions: 'group-data-[state=collapsed]/sidebar:hidden',
body: 'group-data-[state=collapsed]/sidebar:overflow-hidden'
},
none: {
root: 'h-full w-(--sidebar-width)'
}
},
variant: {
sidebar: {},
floating: {
container: 'p-4 border-transparent',
inner: 'rounded-lg ring ring-default shadow-lg',
rail: 'inset-y-4'
},
inset: {
container: 'py-4 border-transparent',
inner: 'divide-transparent',
rail: 'inset-y-4'
}
}
},
compoundVariants: [
{
side: 'left',
collapsible: [
'offcanvas',
'icon'
],
class: {
rail: 'cursor-w-resize rtl:cursor-e-resize data-[state=collapsed]:cursor-e-resize data-[state=collapsed]:rtl:cursor-w-resize'
}
},
{
side: 'right',
collapsible: [
'offcanvas',
'icon'
],
class: {
rail: 'cursor-e-resize rtl:cursor-w-resize data-[state=collapsed]:cursor-w-resize data-[state=collapsed]:rtl:cursor-e-resize'
}
},
{
side: 'left',
collapsible: 'none',
class: {
root: 'border-e border-default'
}
},
{
side: 'right',
collapsible: 'none',
class: {
root: 'border-s border-default'
}
},
{
side: 'left',
collapsible: 'offcanvas',
class: {
container: 'data-[state=collapsed]:-start-(--sidebar-width)'
}
},
{
side: 'right',
collapsible: 'offcanvas',
class: {
container: 'data-[state=collapsed]:-end-(--sidebar-width)'
}
},
{
variant: 'floating',
collapsible: 'icon',
class: {
gap: 'data-[state=collapsed]:w-[calc(var(--sidebar-width-icon)+--spacing(8))]',
container: 'data-[state=collapsed]:w-[calc(var(--sidebar-width-icon)+--spacing(8)+2px)]'
}
},
{
variant: 'floating',
collapsible: 'none',
class: {
root: 'p-4 border-0'
}
},
{
variant: 'inset',
collapsible: 'none',
class: {
root: 'py-4 border-0'
}
},
{
variant: 'floating',
side: 'left',
class: {
rail: 'end-4'
}
},
{
variant: 'floating',
side: 'right',
class: {
rail: 'start-[calc(--spacing(4)-1px)]'
}
}
]
}
}
})import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
ui: {
sidebar: {
slots: {
root: 'peer [--sidebar-width:16rem] [--sidebar-width-icon:4rem]',
gap: 'relative w-(--sidebar-width) bg-transparent',
container: 'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) lg:flex',
inner: 'flex size-full flex-col overflow-hidden divide-y divide-default',
header: 'flex items-center gap-1.5 overflow-hidden px-4 min-h-(--ui-header-height)',
wrapper: 'min-w-0 flex-1',
title: 'text-highlighted font-semibold truncate',
description: 'text-muted text-sm truncate',
actions: 'flex items-center gap-1.5 shrink-0',
close: '',
body: 'flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto p-4',
footer: 'flex items-center gap-1.5 overflow-hidden p-4',
rail: [
'absolute inset-y-0 z-20 hidden w-4 after:absolute after:inset-y-0 after:left-1/2 after:w-px lg:flex hover:after:bg-(--ui-border-accented)',
'after:transition-colors'
]
},
variants: {
transition: {
true: {
gap: 'transition-[width] duration-200 ease-out motion-reduce:transition-none',
container: 'transition-[inset-inline-start,inset-inline-end,width] duration-200 ease-out motion-reduce:transition-none'
}
},
side: {
left: {
container: 'start-0 border-e border-default',
rail: 'end-0 translate-x-1/2 rtl:-translate-x-1/2'
},
right: {
container: 'end-0 border-s border-default',
rail: '-start-px -translate-x-1/2 rtl:translate-x-1/2'
}
},
collapsible: {
offcanvas: {
root: 'group/sidebar hidden lg:block',
gap: 'data-[state=collapsed]:w-0'
},
icon: {
root: 'group/sidebar hidden lg:block',
gap: 'data-[state=collapsed]:w-(--sidebar-width-icon)',
container: 'data-[state=collapsed]:w-(--sidebar-width-icon)',
actions: 'group-data-[state=collapsed]/sidebar:hidden',
body: 'group-data-[state=collapsed]/sidebar:overflow-hidden'
},
none: {
root: 'h-full w-(--sidebar-width)'
}
},
variant: {
sidebar: {},
floating: {
container: 'p-4 border-transparent',
inner: 'rounded-lg ring ring-default shadow-lg',
rail: 'inset-y-4'
},
inset: {
container: 'py-4 border-transparent',
inner: 'divide-transparent',
rail: 'inset-y-4'
}
}
},
compoundVariants: [
{
side: 'left',
collapsible: [
'offcanvas',
'icon'
],
class: {
rail: 'cursor-w-resize rtl:cursor-e-resize data-[state=collapsed]:cursor-e-resize data-[state=collapsed]:rtl:cursor-w-resize'
}
},
{
side: 'right',
collapsible: [
'offcanvas',
'icon'
],
class: {
rail: 'cursor-e-resize rtl:cursor-w-resize data-[state=collapsed]:cursor-w-resize data-[state=collapsed]:rtl:cursor-e-resize'
}
},
{
side: 'left',
collapsible: 'none',
class: {
root: 'border-e border-default'
}
},
{
side: 'right',
collapsible: 'none',
class: {
root: 'border-s border-default'
}
},
{
side: 'left',
collapsible: 'offcanvas',
class: {
container: 'data-[state=collapsed]:-start-(--sidebar-width)'
}
},
{
side: 'right',
collapsible: 'offcanvas',
class: {
container: 'data-[state=collapsed]:-end-(--sidebar-width)'
}
},
{
variant: 'floating',
collapsible: 'icon',
class: {
gap: 'data-[state=collapsed]:w-[calc(var(--sidebar-width-icon)+--spacing(8))]',
container: 'data-[state=collapsed]:w-[calc(var(--sidebar-width-icon)+--spacing(8)+2px)]'
}
},
{
variant: 'floating',
collapsible: 'none',
class: {
root: 'p-4 border-0'
}
},
{
variant: 'inset',
collapsible: 'none',
class: {
root: 'py-4 border-0'
}
},
{
variant: 'floating',
side: 'left',
class: {
rail: 'end-4'
}
},
{
variant: 'floating',
side: 'right',
class: {
rail: 'start-[calc(--spacing(4)-1px)]'
}
}
]
}
}
})
]
})