Sidebar
Bearbeiten
Die Sidebar-Komponente ist eine eigenständige, feste Seitenleiste, die den Seiteninhalt verschiebt. Auf dem Desktop rendert sie inline und kann zusammengeklappt werden; auf dem Handy öffnet sie eine Modal, Slideover oder [Drawer](](/docs/components/drawer) Komponente.
Verwenden Sie die Slots header, default und footer, um den Inhalt der Seitenleiste anzupassen. Die Direktive v-model:open ist viewport-fähig: Auf dem Desktop steuert sie den erweiterten/reduzierten Zustand, auf dem Handy steuert sie das Menü.
<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 Bearbeiten
Verwenden Sie die variant-prop, um den visuellen Stil der Seitenleiste zu ändern. Standardmäßig ist 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>Collapsible ist
Verwenden Sie die collapsible-Prop, um das Kollapsverhalten der Seitenleiste zu ändern. Standardmäßig ist offcanvas.
offcanvas: Die Seitenleiste wird vollständig aus dem Blickfeld verschoben.icon: Die Sidebar schrumpft auf die reine Symbolbreite.none: Die Seitenleiste ist nicht zusammenklappbar.
<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 in den Slot-Requisiten zugreifen, um den Inhalt der Seitenleiste anzupassen, wenn sie zusammengeklappt ist.Side
Verwenden Sie die side-prop, um die Seite der Seitenleiste zu ändern. Standardmäßig ist 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>Titel
Verwenden Sie die title-Prop, um den Titel des Sidebar-Headers festzulegen.
<template>
<USidebar title="Navigation">
<Placeholder class="h-full" />
</USidebar>
</template>Beschreibung
Verwenden Sie die description-Prop, um die Beschreibung des Sidebar-Headers festzulegen.
<template>
<USidebar title="Navigation" description="Browse your workspace">
<Placeholder class="h-full" />
</USidebar>
</template>Rail (englisch)
Verwenden Sie die rail-Prop, um eine dünne interaktive Kante in der Seitenleiste anzuzeigen, die beim Klicken den zusammengebrochenen Zustand umschaltet. Die Schiene wird nur gerendert, wenn collapsible nicht none ist.
<template>
<USidebar rail collapsible="icon" title="Navigation">
<Placeholder class="h-full" />
</USidebar>
</template>Close
Verwenden Sie die close-Prop, um eine Schließen-Schaltfläche im Sidebar-Header anzuzeigen. Die Schließen-Schaltfläche wird nur gerendert, wenn collapsible nicht none ist.
Sie können jede Eigenschaft der Komponente Button übergeben, um sie anzupassen.
<template>
<USidebar close rail collapsible="icon" title="Navigation">
<Placeholder class="h-full" />
</USidebar>
</template>Schließen Icon
Verwenden Sie die close-icon-Prop, um die Schließen-Taste Icon. Defaults auf i-lucide-x.
<template>
<USidebar
close
close-icon="i-lucide-panel-right-close"
rail
collapsible="icon"
side="right"
title="Navigation"
>
<Placeholder class="h-full" />
</USidebar>
</template>vite.config.ts unter dem ui.icons.close-Schlüssel anpassen.Mode ist
Verwenden Sie die mode-prop, um den Modus des Seitenleistenmenüs auf mobile zu ändern. Standardmäßig auf 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-prop verwenden, um das Menü der Seitenleiste anzupassen, es wird sich je nach dem von Ihnen gewählten Modus anpassen.Examples Bearbeiten
Control im offenen Zustand
Sie können den offenen Zustand steuern, indem Sie die open prop oder die v-model:open Direktive verwenden. Auf dem Desktop steuert es den expandierten/kollabierten Zustand, auf dem Handy öffnet/schließt es das Blattmenü.
<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 den offenen Status der Sidebar durch Drücken von kbd{value="O"} umschalten.Persistieren im offenen Zustand
Verwenden Sie useLocalStorage von VueUse oder useCookie anstelle von ref, um den Sidebar-Status über das Nachladen der Seite hinweg beizubehalten.
<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) durch useLocalStorage('sidebar-open', true).With custom width Benutzerdefinierte Breite
Die Breite der Seitenleiste wird durch die CSS-Variable --sidebar-width gesteuert (Standardeinstellung ist 16rem). Die reduzierte Symbolbreite wird von --sidebar-width-icon gesteuert (Standardeinstellung ist 4rem).
Überschreiben Sie sie global in Ihrem CSS oder pro Instanz mit dem 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>With header (englisch)
Um die Seitenleiste unter einem Header zu positionieren, passen Sie die gap und container mit der ui-Prop an.
<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 ist standardmäßig auf 4rem eingestellt und wird vom Header verwendet. Passen Sie sie an, wenn Ihre Navigationsleiste eine andere Höhe verwendet.Mit KI Chat
Verwenden Sie die Seitenleiste auf der rechten Seite mit ChatMessages und [ChatPrompt](/docs/components/chat-promptxph37x, um ein AI-Chat-Panel zu erstellen.
<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 Bearbeiten
Props (nicht)
| 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 (englisch)
| 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; } |
Theme Bearbeiten
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)]'
}
}
]
}
}
})
]
})