Sidebar Soon

GitHub
A collapsible sidebar with multiple visual variants.

Usage

The Sidebar component is a standalone, fixed sidebar that pushes the page content. On desktop, it renders inline and can be collapsed; on mobile, it opens as a sheet (Modal, Slideover or Drawer).

Sidebar vs DashboardSidebar: This component is a simple, standalone sidebar you can drop anywhere (chat panel, settings, navigation). If you need drag-to-resize, state persistence and integration with DashboardGroup, use DashboardSidebar instead.

Use the title, description and close props to customize the sidebar header just like the Modal, Slideover and Drawer components.

Use the body, default and footer slots to customize the sidebar content. The v-model:open directive is viewport-aware: on desktop it controls the expanded/collapsed state, on mobile it controls the sheet menu.

Page Title
<script setup lang="ts">
import type { NavigationMenuItem } from '@nuxt/ui'

const route = useRoute()

const variant = computed(() => (route.query.variant as 'sidebar' | 'floating' | 'inset') || 'sidebar')
const collapsible = computed(() => (route.query.collapsible as 'offcanvas' | 'icon' | 'none') || 'icon')
const side = computed(() => (route.query.side as 'left' | 'right') || 'left')
const mode = computed(() => (route.query.mode as 'modal' | 'slideover' | 'drawer') || 'slideover')

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'
}, {
  label: 'Settings',
  icon: 'i-lucide-settings'
}]
</script>

<template>
  <div class="flex h-full w-full [contain:paint]">
    <USidebar
      v-model:open="open"
      :variant="variant"
      :collapsible="collapsible"
      :side="side"
      :mode="mode"
      title="Navigation"
      close
      :ui="{ container: 'absolute' }"
    >
      <template #default="{ state }">
        <UNavigationMenu
          :collapsed="state === 'collapsed'"
          :items="items"
          orientation="vertical"
        />
      </template>

      <template #footer="{ state }">
        <UButton
          :avatar="{ src: 'https://github.com/benjamincanac.png' }"
          :label="state === 'collapsed' ? undefined : 'Benjamin'"
          color="neutral"
          variant="ghost"
          class="w-full"
        />
      </template>
    </USidebar>

    <div class="flex-1 flex flex-col">
      <div class="h-(--ui-header-height) shrink-0 flex items-center gap-2 px-4 border-b border-default">
        <UButton
          icon="i-lucide-panel-left"
          color="neutral"
          variant="ghost"
          @click="open = !open"
        />

        <span class="font-semibold text-sm">Page Title</span>
      </div>

      <div class="flex-1" />
    </div>
  </div>
</template>

Variant

Use the variant prop to change the visual style of the sidebar. Defaults to sidebar.

Page Title
<script setup lang="ts">
import type { NavigationMenuItem } from '@nuxt/ui'

const route = useRoute()

const variant = computed(() => (route.query.variant as 'sidebar' | 'floating' | 'inset') || 'sidebar')
const collapsible = computed(() => (route.query.collapsible as 'offcanvas' | 'icon' | 'none') || 'icon')
const side = computed(() => (route.query.side as 'left' | 'right') || 'left')
const mode = computed(() => (route.query.mode as 'modal' | 'slideover' | 'drawer') || 'slideover')

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'
}, {
  label: 'Settings',
  icon: 'i-lucide-settings'
}]
</script>

<template>
  <div class="flex h-full w-full [contain:paint]">
    <USidebar
      v-model:open="open"
      :variant="variant"
      :collapsible="collapsible"
      :side="side"
      :mode="mode"
      title="Navigation"
      close
      :ui="{ container: 'absolute' }"
    >
      <template #default="{ state }">
        <UNavigationMenu
          :collapsed="state === 'collapsed'"
          :items="items"
          orientation="vertical"
        />
      </template>

      <template #footer="{ state }">
        <UButton
          :avatar="{ src: 'https://github.com/benjamincanac.png' }"
          :label="state === 'collapsed' ? undefined : 'Benjamin'"
          color="neutral"
          variant="ghost"
          class="w-full"
        />
      </template>
    </USidebar>

    <div class="flex-1 flex flex-col">
      <div class="h-(--ui-header-height) shrink-0 flex items-center gap-2 px-4 border-b border-default">
        <UButton
          icon="i-lucide-panel-left"
          color="neutral"
          variant="ghost"
          @click="open = !open"
        />

        <span class="font-semibold text-sm">Page Title</span>
      </div>

      <div class="flex-1" />
    </div>
  </div>
</template>

Collapsible

Use the collapsible prop to change the collapse behavior of the sidebar. Defaults to none.

  • offcanvas: The sidebar slides out of view completely.
  • icon: The sidebar shrinks to icon-only width.
  • none: The sidebar is not collapsible.
Page Title
<script setup lang="ts">
import type { NavigationMenuItem } from '@nuxt/ui'

const route = useRoute()

const variant = computed(() => (route.query.variant as 'sidebar' | 'floating' | 'inset') || 'sidebar')
const collapsible = computed(() => (route.query.collapsible as 'offcanvas' | 'icon' | 'none') || 'icon')
const side = computed(() => (route.query.side as 'left' | 'right') || 'left')
const mode = computed(() => (route.query.mode as 'modal' | 'slideover' | 'drawer') || 'slideover')

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'
}, {
  label: 'Settings',
  icon: 'i-lucide-settings'
}]
</script>

<template>
  <div class="flex h-full w-full [contain:paint]">
    <USidebar
      v-model:open="open"
      :variant="variant"
      :collapsible="collapsible"
      :side="side"
      :mode="mode"
      title="Navigation"
      close
      :ui="{ container: 'absolute' }"
    >
      <template #default="{ state }">
        <UNavigationMenu
          :collapsed="state === 'collapsed'"
          :items="items"
          orientation="vertical"
        />
      </template>

      <template #footer="{ state }">
        <UButton
          :avatar="{ src: 'https://github.com/benjamincanac.png' }"
          :label="state === 'collapsed' ? undefined : 'Benjamin'"
          color="neutral"
          variant="ghost"
          class="w-full"
        />
      </template>
    </USidebar>

    <div class="flex-1 flex flex-col">
      <div class="h-(--ui-header-height) shrink-0 flex items-center gap-2 px-4 border-b border-default">
        <UButton
          icon="i-lucide-panel-left"
          color="neutral"
          variant="ghost"
          @click="open = !open"
        />

        <span class="font-semibold text-sm">Page Title</span>
      </div>

      <div class="flex-1" />
    </div>
  </div>
</template>
You can access the state in the slot props to customize the content of the sidebar when it is collapsed.

Side

Use the side prop to change the side of the sidebar. Defaults to left.

Page Title
<script setup lang="ts">
import type { NavigationMenuItem } from '@nuxt/ui'

const route = useRoute()

const variant = computed(() => (route.query.variant as 'sidebar' | 'floating' | 'inset') || 'sidebar')
const collapsible = computed(() => (route.query.collapsible as 'offcanvas' | 'icon' | 'none') || 'icon')
const side = computed(() => (route.query.side as 'left' | 'right') || 'left')
const mode = computed(() => (route.query.mode as 'modal' | 'slideover' | 'drawer') || 'slideover')

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'
}, {
  label: 'Settings',
  icon: 'i-lucide-settings'
}]
</script>

<template>
  <div class="flex h-full w-full [contain:paint]">
    <USidebar
      v-model:open="open"
      :variant="variant"
      :collapsible="collapsible"
      :side="side"
      :mode="mode"
      title="Navigation"
      close
      :ui="{ container: 'absolute' }"
    >
      <template #default="{ state }">
        <UNavigationMenu
          :collapsed="state === 'collapsed'"
          :items="items"
          orientation="vertical"
        />
      </template>

      <template #footer="{ state }">
        <UButton
          :avatar="{ src: 'https://github.com/benjamincanac.png' }"
          :label="state === 'collapsed' ? undefined : 'Benjamin'"
          color="neutral"
          variant="ghost"
          class="w-full"
        />
      </template>
    </USidebar>

    <div class="flex-1 flex flex-col">
      <div class="h-(--ui-header-height) shrink-0 flex items-center gap-2 px-4 border-b border-default">
        <UButton
          icon="i-lucide-panel-left"
          color="neutral"
          variant="ghost"
          @click="open = !open"
        />

        <span class="font-semibold text-sm">Page Title</span>
      </div>

      <div class="flex-1" />
    </div>
  </div>
</template>

Title

Use the title prop to set the title of the sidebar header.

Page Title
<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 h-full w-full [contain:paint]">
    <USidebar
      v-model:open="open"
      collapsible="offcanvas"
      title="Navigation"
      :ui="{ container: 'absolute' }"
    >
      <UNavigationMenu
        :items="items"
        orientation="vertical"
      />
    </USidebar>

    <div class="flex-1 flex flex-col">
      <div class="h-(--ui-header-height) shrink-0 flex items-center gap-2 px-4 border-b border-default">
        <UButton
          icon="i-lucide-panel-left"
          color="neutral"
          variant="ghost"
          @click="open = !open"
        />

        <span class="font-semibold text-sm">Page Title</span>
      </div>

      <div class="flex-1" />
    </div>
  </div>
</template>

Description

Use the description prop to set the description of the sidebar header.

Page Title
<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 h-full w-full [contain:paint]">
    <USidebar
      v-model:open="open"
      collapsible="offcanvas"
      title="Navigation"
      description="Browse your workspace"
      :ui="{ container: 'absolute' }"
    >
      <UNavigationMenu
        :items="items"
        orientation="vertical"
      />
    </USidebar>

    <div class="flex-1 flex flex-col">
      <div class="h-(--ui-header-height) shrink-0 flex items-center gap-2 px-4 border-b border-default">
        <UButton
          icon="i-lucide-panel-left"
          color="neutral"
          variant="ghost"
          @click="open = !open"
        />

        <span class="font-semibold text-sm">Page Title</span>
      </div>

      <div class="flex-1" />
    </div>
  </div>
</template>

Close

Use the close prop to display a close button in the sidebar header. The close button is only rendered when collapsible is not none.

You can pass any property from the Button component to customize it.

Page Title
<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 h-full w-full [contain:paint]">
    <USidebar
      v-model:open="open"
      collapsible="offcanvas"
      title="Navigation"
      close
      :ui="{ container: 'absolute' }"
    >
      <UNavigationMenu
        :items="items"
        orientation="vertical"
      />
    </USidebar>

    <div class="flex-1 flex flex-col">
      <div class="h-(--ui-header-height) shrink-0 flex items-center gap-2 px-4 border-b border-default">
        <UButton
          icon="i-lucide-panel-left"
          color="neutral"
          variant="ghost"
          @click="open = !open"
        />

        <span class="font-semibold text-sm">Page Title</span>
      </div>

      <div class="flex-1" />
    </div>
  </div>
</template>

Close Icon

Use the close-icon prop to customize the close button Icon. Defaults to i-lucide-x.

Page Title
<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 h-full w-full [contain:paint]">
    <USidebar
      v-model:open="open"
      collapsible="offcanvas"
      title="Navigation"
      close
      close-icon="i-lucide-arrow-left"
      :ui="{ container: 'absolute' }"
    >
      <UNavigationMenu
        :items="items"
        orientation="vertical"
      />
    </USidebar>

    <div class="flex-1 flex flex-col">
      <div class="h-(--ui-header-height) shrink-0 flex items-center gap-2 px-4 border-b border-default">
        <UButton
          icon="i-lucide-panel-left"
          color="neutral"
          variant="ghost"
          @click="open = !open"
        />

        <span class="font-semibold text-sm">Page Title</span>
      </div>

      <div class="flex-1" />
    </div>
  </div>
</template>
You can use the #title, #description and #close slots to customize them.

Width

The sidebar width is controlled by the --sidebar-width CSS variable (defaults to 28rem). The collapsed icon width is controlled by --sidebar-width-icon (defaults to 4rem).

Override them globally in your CSS or per-instance with the style attribute:

<USidebar :style="{ '--sidebar-width': '20rem' }" />

With Navbar

To position the sidebar below a fixed navbar, customize the container position using the ui prop:

<USidebar
  :ui="{
    gap: 'h-[calc(100vh-var(--ui-header-height))]',
    container: 'top-[var(--ui-header-height)] bottom-0 h-[calc(100vh-var(--ui-header-height))]'
  }"
/>
The --ui-header-height variable defaults to 4rem and is used by the Header and DashboardNavbar components. Adjust it if your navbar uses a different height.

Mode

Use the mode prop to change the mode of the sidebar menu on mobile. Defaults to slideover.

Page Title
<script setup lang="ts">
import type { NavigationMenuItem } from '@nuxt/ui'

const route = useRoute()

const variant = computed(() => (route.query.variant as 'sidebar' | 'floating' | 'inset') || 'sidebar')
const collapsible = computed(() => (route.query.collapsible as 'offcanvas' | 'icon' | 'none') || 'icon')
const side = computed(() => (route.query.side as 'left' | 'right') || 'left')
const mode = computed(() => (route.query.mode as 'modal' | 'slideover' | 'drawer') || 'slideover')

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'
}, {
  label: 'Settings',
  icon: 'i-lucide-settings'
}]
</script>

<template>
  <div class="flex h-full w-full [contain:paint]">
    <USidebar
      v-model:open="open"
      :variant="variant"
      :collapsible="collapsible"
      :side="side"
      :mode="mode"
      title="Navigation"
      close
      :ui="{ container: 'absolute' }"
    >
      <template #default="{ state }">
        <UNavigationMenu
          :collapsed="state === 'collapsed'"
          :items="items"
          orientation="vertical"
        />
      </template>

      <template #footer="{ state }">
        <UButton
          :avatar="{ src: 'https://github.com/benjamincanac.png' }"
          :label="state === 'collapsed' ? undefined : 'Benjamin'"
          color="neutral"
          variant="ghost"
          class="w-full"
        />
      </template>
    </USidebar>

    <div class="flex-1 flex flex-col">
      <div class="h-(--ui-header-height) shrink-0 flex items-center gap-2 px-4 border-b border-default">
        <UButton
          icon="i-lucide-panel-left"
          color="neutral"
          variant="ghost"
          @click="open = !open"
        />

        <span class="font-semibold text-sm">Page Title</span>
      </div>

      <div class="flex-1" />
    </div>
  </div>
</template>
You can use the menu prop to customize the menu of the sidebar, it will adapt depending on the mode you choose.

Examples

Control open state

You can control the open state by using the open prop or the v-model:open directive. On desktop it controls the expanded/collapsed state, on mobile it opens/closes the sheet menu.

Page Title
<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'
}, {
  label: 'Contacts',
  icon: 'i-lucide-users'
}]
</script>

<template>
  <div class="flex h-full w-full [contain:paint]">
    <USidebar v-model:open="open" collapsible="icon" :ui="{ container: 'absolute' }">
      <template #header="{ state }">
        <span v-if="state === 'expanded'" class="font-semibold text-sm">Navigation</span>
      </template>

      <UNavigationMenu
        :collapsed="!open"
        :items="items"
        orientation="vertical"
      />
    </USidebar>

    <div class="flex-1 flex flex-col">
      <div class="h-(--ui-header-height) shrink-0 flex items-center gap-2 px-4 border-b border-default">
        <UButton
          icon="i-lucide-panel-left"
          color="neutral"
          variant="ghost"
          @click="open = !open"
        />

        <span class="font-semibold text-sm">Page Title</span>
      </div>

      <div class="flex-1" />
    </div>
  </div>
</template>
In this example, leveraging defineShortcuts, you can toggle the open state of the Sidebar by pressing O.

Persist open state

Use useLocalStorage from VueUse or useCookie instead of ref to persist the sidebar state across page reloads.

Page Title
<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'
}, {
  label: 'Contacts',
  icon: 'i-lucide-users'
}]
</script>

<template>
  <div class="flex h-full w-full [contain:paint]">
    <USidebar v-model:open="open" collapsible="icon" :ui="{ container: 'absolute' }">
      <template #header="{ state }">
        <span v-if="state === 'expanded'" class="font-semibold text-sm">Navigation</span>
      </template>

      <UNavigationMenu
        :collapsed="!open"
        :items="items"
        orientation="vertical"
      />
    </USidebar>

    <div class="flex-1 flex flex-col">
      <div class="h-(--ui-header-height) shrink-0 flex items-center gap-2 px-4 border-b border-default">
        <UButton
          icon="i-lucide-panel-left"
          color="neutral"
          variant="ghost"
          @click="open = !open"
        />

        <span class="font-semibold text-sm">Page Title</span>
      </div>

      <div class="flex-1" />
    </div>
  </div>
</template>
The only difference with the previous example is replacing ref(true) with useLocalStorage('sidebar-open', true).

API

Props

Prop Default Type
as'aside'any

The element or component this component should render as.

variant'sidebar' "floating" | "sidebar" | "inset"

The visual variant of the sidebar.

collapsible'none' "offcanvas" | "icon" | "none"

The collapse behavior of the sidebar.

  • offcanvas: The sidebar slides out of view completely.
  • icon: The sidebar shrinks to icon-only width.
  • none: The sidebar is not collapsible.
side'left' "left" | "right"

The side to render the sidebar on.

title string

The title displayed in the sidebar header.

description string

The description displayed in the sidebar header.

closefalseboolean | Omit<ButtonProps, LinkPropsKeys>

Display a close button to collapse the sidebar. Only renders when collapsible is not none. { size: 'md', color: 'neutral', variant: 'ghost' }

closeIconappConfig.ui.icons.closeany

The icon displayed in the close button.

railfalseboolean

Display a rail on the sidebar edge to toggle collapse. Only renders when collapsible is not none.

mode'slideover' T

The mode of the sidebar menu on mobile.

menu SidebarMenu<T>

The props for the sidebar menu component on mobile.

opentrueboolean
ui { root?: ClassNameValue; gap?: ClassNameValue; container?: ClassNameValue; inner?: ClassNameValue; header?: ClassNameValue; wrapper?: ClassNameValue; title?: ClassNameValue; description?: ClassNameValue; close?: ClassNameValue; body?: ClassNameValue; footer?: ClassNameValue; rail?: ClassNameValue; overlay?: ClassNameValue; }

Slots

Slot Type
header{ state: SidebarState; open: boolean; close: () => void; }
title{}
description{}
actions{}
close{ ui: object; }
body{ state: SidebarState; open: boolean; close: () => void; }
default{ state: SidebarState; open: boolean; close: () => void; }
footer{ state: SidebarState; open: boolean; close: () => void; }
rail{ ui: object; }
content{ close: () => void; }

Theme

app.config.ts
export default defineAppConfig({
  ui: {
    sidebar: {
      slots: {
        root: 'peer [--sidebar-width:16rem] [--sidebar-width-icon:4rem]',
        gap: 'relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear',
        container: 'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear lg:flex',
        inner: 'flex size-full flex-col overflow-hidden bg-default divide-y divide-default',
        header: 'flex items-center gap-1.5 p-4 min-h-16',
        wrapper: '',
        title: 'text-highlighted font-semibold',
        description: 'mt-1 text-muted text-sm',
        close: 'absolute top-4 end-4',
        body: 'flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto p-4',
        footer: 'flex items-center gap-1.5 p-4',
        rail: [
          'absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-px lg:flex hover:after:bg-(--ui-border-accented)',
          'after:transition-colors'
        ],
        overlay: 'lg:hidden'
      },
      variants: {
        side: {
          left: {
            container: 'left-0 border-e border-default'
          },
          right: {
            container: 'right-0 border-s border-default'
          }
        },
        collapsible: {
          offcanvas: {
            root: 'group/sidebar hidden lg:block',
            gap: 'data-[state=collapsed]:w-0',
            container: '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)',
            header: 'group-data-[state=collapsed]/sidebar:overflow-hidden group-data-[state=collapsed]/sidebar:p-2',
            body: 'group-data-[state=collapsed]/sidebar:overflow-hidden',
            footer: 'group-data-[state=collapsed]/sidebar:overflow-hidden group-data-[state=collapsed]/sidebar:p-2'
          },
          none: {
            root: 'h-full w-(--sidebar-width)'
          }
        },
        variant: {
          sidebar: {},
          floating: {
            container: 'p-2 border-0',
            inner: 'rounded-lg border border-default shadow-sm'
          },
          inset: {
            container: 'p-2 border-0',
            inner: 'rounded-lg'
          }
        }
      },
      compoundVariants: [
        {
          side: 'left',
          collapsible: 'offcanvas',
          class: {
            rail: 'end-0 translate-x-1/2 data-[state=collapsed]:cursor-e-resize cursor-w-resize'
          }
        },
        {
          side: 'right',
          collapsible: 'offcanvas',
          class: {
            rail: '-start-px -translate-x-1/2 data-[state=collapsed]:cursor-w-resize cursor-e-resize'
          }
        },
        {
          side: 'left',
          collapsible: 'icon',
          class: {
            rail: 'end-0 translate-x-1/2 data-[state=collapsed]:cursor-e-resize cursor-w-resize'
          }
        },
        {
          side: 'right',
          collapsible: 'icon',
          class: {
            rail: '-start-px -translate-x-1/2 data-[state=collapsed]:cursor-w-resize 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]:-left-(--sidebar-width)'
          }
        },
        {
          side: 'right',
          collapsible: 'offcanvas',
          class: {
            container: 'data-[state=collapsed]:-right-(--sidebar-width)'
          }
        },
        {
          variant: 'floating',
          collapsible: 'icon',
          class: {
            gap: 'data-[state=collapsed]:w-[calc(var(--sidebar-width-icon)+theme(spacing.4))]',
            container: 'data-[state=collapsed]:w-[calc(var(--sidebar-width-icon)+theme(spacing.4)+2px)]'
          }
        },
        {
          variant: 'inset',
          collapsible: 'icon',
          class: {
            gap: 'data-[state=collapsed]:w-[calc(var(--sidebar-width-icon)+theme(spacing.4))]',
            container: 'data-[state=collapsed]:w-[calc(var(--sidebar-width-icon)+theme(spacing.4)+2px)]'
          }
        }
      ]
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        sidebar: {
          slots: {
            root: 'peer [--sidebar-width:16rem] [--sidebar-width-icon:4rem]',
            gap: 'relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear',
            container: 'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear lg:flex',
            inner: 'flex size-full flex-col overflow-hidden bg-default divide-y divide-default',
            header: 'flex items-center gap-1.5 p-4 min-h-16',
            wrapper: '',
            title: 'text-highlighted font-semibold',
            description: 'mt-1 text-muted text-sm',
            close: 'absolute top-4 end-4',
            body: 'flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto p-4',
            footer: 'flex items-center gap-1.5 p-4',
            rail: [
              'absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-px lg:flex hover:after:bg-(--ui-border-accented)',
              'after:transition-colors'
            ],
            overlay: 'lg:hidden'
          },
          variants: {
            side: {
              left: {
                container: 'left-0 border-e border-default'
              },
              right: {
                container: 'right-0 border-s border-default'
              }
            },
            collapsible: {
              offcanvas: {
                root: 'group/sidebar hidden lg:block',
                gap: 'data-[state=collapsed]:w-0',
                container: '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)',
                header: 'group-data-[state=collapsed]/sidebar:overflow-hidden group-data-[state=collapsed]/sidebar:p-2',
                body: 'group-data-[state=collapsed]/sidebar:overflow-hidden',
                footer: 'group-data-[state=collapsed]/sidebar:overflow-hidden group-data-[state=collapsed]/sidebar:p-2'
              },
              none: {
                root: 'h-full w-(--sidebar-width)'
              }
            },
            variant: {
              sidebar: {},
              floating: {
                container: 'p-2 border-0',
                inner: 'rounded-lg border border-default shadow-sm'
              },
              inset: {
                container: 'p-2 border-0',
                inner: 'rounded-lg'
              }
            }
          },
          compoundVariants: [
            {
              side: 'left',
              collapsible: 'offcanvas',
              class: {
                rail: 'end-0 translate-x-1/2 data-[state=collapsed]:cursor-e-resize cursor-w-resize'
              }
            },
            {
              side: 'right',
              collapsible: 'offcanvas',
              class: {
                rail: '-start-px -translate-x-1/2 data-[state=collapsed]:cursor-w-resize cursor-e-resize'
              }
            },
            {
              side: 'left',
              collapsible: 'icon',
              class: {
                rail: 'end-0 translate-x-1/2 data-[state=collapsed]:cursor-e-resize cursor-w-resize'
              }
            },
            {
              side: 'right',
              collapsible: 'icon',
              class: {
                rail: '-start-px -translate-x-1/2 data-[state=collapsed]:cursor-w-resize 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]:-left-(--sidebar-width)'
              }
            },
            {
              side: 'right',
              collapsible: 'offcanvas',
              class: {
                container: 'data-[state=collapsed]:-right-(--sidebar-width)'
              }
            },
            {
              variant: 'floating',
              collapsible: 'icon',
              class: {
                gap: 'data-[state=collapsed]:w-[calc(var(--sidebar-width-icon)+theme(spacing.4))]',
                container: 'data-[state=collapsed]:w-[calc(var(--sidebar-width-icon)+theme(spacing.4)+2px)]'
              }
            },
            {
              variant: 'inset',
              collapsible: 'icon',
              class: {
                gap: 'data-[state=collapsed]:w-[calc(var(--sidebar-width-icon)+theme(spacing.4))]',
                container: 'data-[state=collapsed]:w-[calc(var(--sidebar-width-icon)+theme(spacing.4)+2px)]'
              }
            }
          ]
        }
      }
    })
  ]
})

Changelog

No recent changes