v2.5.3

Sidebar Soon

A collapsible sidebar with multiple visual style options.

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 a Modal, Slideover or Drawer component.

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 header, 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 menu.

<script setup lang="ts">
import type { DropdownMenuItem, NavigationMenuItem } from '@bitrix24/b24ui-nuxt'
import HomeIcon from '@bitrix24/b24icons-vue/outline/HomeIcon'
import MailIcon from '@bitrix24/b24icons-vue/outline/MailIcon'
import PaymentIcon from '@bitrix24/b24icons-vue/outline/PaymentIcon'
import CirclePlusIcon from '@bitrix24/b24icons-vue/outline/CirclePlusIcon'
import PulseCircleIcon from '@bitrix24/b24icons-vue/main/PulseCircleIcon'
import CollabIcon from '@bitrix24/b24icons-vue/outline/CollabIcon'
import SettingsIcon from '@bitrix24/b24icons-vue/main/SettingsIcon'
import ThreePersonsIcon from '@bitrix24/b24icons-vue/outline/ThreePersonsIcon'
import UserIcon from '@bitrix24/b24icons-vue/common-b24/UserIcon'
import ContrastIcon from '@bitrix24/b24icons-vue/outline/ContrastIcon'
import SunIcon from '@bitrix24/b24icons-vue/outline/SunIcon'
import MoonIcon from '@bitrix24/b24icons-vue/outline/MoonIcon'
import GitHubIcon from '@bitrix24/b24icons-vue/social/GitHubIcon'
import LogOutIcon from '@bitrix24/b24icons-vue/outline/LogOutIcon'
import CloseChatIcon from '@bitrix24/b24icons-vue/outline/CloseChatIcon'

const open = ref(true)

const colorMode = useColorMode()

const teams = ref([
  {
    label: 'Assistant Name',
    avatar: {
      src: '/b24ui/avatar/assistant.png',
      alt: 'assistant',
      loading: 'lazy' as const
    }
  },
  {
    label: 'Bitrix24',
    avatar: {
      src: 'https://github.com/bitrix24.png',
      alt: 'bitrix24',
      loading: 'lazy' as const
    }
  },
  {
    label: 'Employee Name',
    avatar: {
      src: '/b24ui/avatar/employee.png',
      alt: 'employee',
      loading: 'lazy' as const
    }
  }
])
const selectedTeam = ref(teams.value[0])
const selectedTeamFormatted = computed(() => {
  return open.value
    ? selectedTeam.value
    : {
        avatar: selectedTeam.value!.avatar
      }
})

const teamsItems = computed<DropdownMenuItem[][]>(() => {
  return [
    teams.value.map((team, index) => ({
      ...team,
      kbds: ['meta', String(index + 1)],
      onSelect() {
        selectedTeam.value = team
      }
    })),
    [{ label: 'Create team', icon: CirclePlusIcon }]
  ]
})

function getItems(state: 'collapsed' | 'expanded') {
  return [
    {
      label: 'Inbox',
      icon: MailIcon,
      badge: '4'
    },
    {
      label: 'Issues',
      icon: CollabIcon
    },
    {
      label: 'Actions',
      icon: PulseCircleIcon
    },
    {
      label: 'Settings',
      icon: SettingsIcon,
      defaultOpen: true,
      children:
        state === 'expanded'
          ? [
              {
                label: 'General',
                icon: HomeIcon
              },
              {
                label: 'Team',
                icon: ThreePersonsIcon
              },
              {
                label: 'Billing',
                icon: PaymentIcon
              }
            ]
          : []
    }
  ] satisfies NavigationMenuItem[]
}

const user = ref({
  name: 'Bitrix24',
  avatar: {
    src: 'https://github.com/bitrix24.png',
    alt: 'bitrix24'
  }
})

const userItems = computed<DropdownMenuItem[][]>(() => [
  [
    {
      label: 'Profile',
      icon: UserIcon
    },
    {
      label: 'Billing',
      icon: PaymentIcon
    },
    {
      label: 'Settings',
      icon: SettingsIcon,
      to: '/templates/'
    }
  ],
  [
    {
      label: 'Appearance',
      icon: ContrastIcon,
      children: [
        {
          label: 'Light',
          icon: SunIcon,
          type: 'checkbox',
          checked: colorMode.value === 'light',
          onUpdateChecked(checked: boolean) {
            if (checked) {
              colorMode.preference = 'light'
            }
          },
          onSelect(e: Event) {
            e.preventDefault()
          }
        },
        {
          label: 'Dark',
          icon: MoonIcon,
          type: 'checkbox',
          checked: colorMode.value === 'dark',
          onUpdateChecked(checked: boolean) {
            if (checked) {
              colorMode.preference = 'dark'
            }
          },
          onSelect(e: Event) {
            e.preventDefault()
          }
        }
      ]
    }
  ],
  [
    {
      label: 'GitHub',
      icon: GitHubIcon,
      to: 'https://github.com/bitrix24/b24ui',
      target: '_blank'
    },
    {
      label: 'Log out',
      icon: LogOutIcon
    }
  ]
])

defineShortcuts(extractShortcuts(teamsItems.value))
</script>

<template>
  <div class="flex flex-1">
    <B24Sidebar
      v-model:open="open"
      collapsible="icon"
      rail
      :b24ui="{
        container: 'h-full',
        inner: 'bg-elevated/25 divide-transparent',
        body: 'py-0'
      }"
    >
      <template #header>
        <B24DropdownMenu
          :items="teamsItems"
          :content="{ align: 'start', collisionPadding: 12 }"
          :b24ui="{ content: 'w-[270px]', viewport: 'w-[270px] max-h-[62vh]' }"
        >
          <B24Button
            v-bind="selectedTeamFormatted"
            :use-dropdown="open"
            color="air-tertiary"
            class="w-full data-[state=open]:bg-(--ui-btn-background-hover) overflow-hidden"
            :b24ui="{ trailingIcon: 'text-dimmed ms-auto', label: 'flex-1' }"
          />
        </B24DropdownMenu>
      </template>

      <template #default="{ state }">
        <B24NavigationMenu
          :key="state"
          :items="getItems(state)"
          orientation="vertical"
          :b24ui="{ link: 'p-1.5 overflow-hidden' }"
        />
      </template>

      <template #footer>
        <B24DropdownMenu
          :items="userItems"
          :content="{ align: 'start', side: 'top', sideOffset: 4, collisionPadding: 12 }"
          :b24ui="{ content: 'w-[200px]', viewport: 'w-[200px] max-h-[62vh]' }"
        >
          <B24Button
            v-bind="user"
            :label="open ? user?.name : undefined"
            :use-dropdown="open"
            color="air-tertiary"
            class="w-full data-[state=open]:bg-(--ui-btn-background-hover) overflow-hidden"
            :b24ui="{ trailingIcon: 'text-dimmed ms-auto', label: 'flex-1' }"
          />
        </B24DropdownMenu>
      </template>
    </B24Sidebar>

    <div class="flex-1 flex flex-col">
      <div
        class="h-(--b24ui-header-height) shrink-0 flex items-center px-4 border-b border-default"
      >
        <B24Button
          :icon="CloseChatIcon"
          aria-label="Toggle sidebar"
          size="md"
          color="air-tertiary"
          @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 type { DropdownMenuItem, NavigationMenuItem } from '@bitrix24/b24ui-nuxt'
import HomeIcon from '@bitrix24/b24icons-vue/outline/HomeIcon'
import MailIcon from '@bitrix24/b24icons-vue/outline/MailIcon'
import PaymentIcon from '@bitrix24/b24icons-vue/outline/PaymentIcon'
import CirclePlusIcon from '@bitrix24/b24icons-vue/outline/CirclePlusIcon'
import PulseCircleIcon from '@bitrix24/b24icons-vue/main/PulseCircleIcon'
import CollabIcon from '@bitrix24/b24icons-vue/outline/CollabIcon'
import SettingsIcon from '@bitrix24/b24icons-vue/main/SettingsIcon'
import ThreePersonsIcon from '@bitrix24/b24icons-vue/outline/ThreePersonsIcon'
import UserIcon from '@bitrix24/b24icons-vue/common-b24/UserIcon'
import ContrastIcon from '@bitrix24/b24icons-vue/outline/ContrastIcon'
import SunIcon from '@bitrix24/b24icons-vue/outline/SunIcon'
import MoonIcon from '@bitrix24/b24icons-vue/outline/MoonIcon'
import GitHubIcon from '@bitrix24/b24icons-vue/social/GitHubIcon'
import LogOutIcon from '@bitrix24/b24icons-vue/outline/LogOutIcon'
import CloseChatIcon from '@bitrix24/b24icons-vue/outline/CloseChatIcon'

const open = ref(true)

const colorMode = useColorMode()

const teams = ref([
  {
    label: 'Assistant Name',
    avatar: {
      src: '/b24ui/avatar/assistant.png',
      alt: 'assistant',
      loading: 'lazy' as const
    }
  },
  {
    label: 'Bitrix24',
    avatar: {
      src: 'https://github.com/bitrix24.png',
      alt: 'bitrix24',
      loading: 'lazy' as const
    }
  },
  {
    label: 'Employee Name',
    avatar: {
      src: '/b24ui/avatar/employee.png',
      alt: 'employee',
      loading: 'lazy' as const
    }
  }
])
const selectedTeam = ref(teams.value[0])
const selectedTeamFormatted = computed(() => {
  return open.value
    ? selectedTeam.value
    : {
        avatar: selectedTeam.value!.avatar
      }
})

const teamsItems = computed<DropdownMenuItem[][]>(() => {
  return [
    teams.value.map((team, index) => ({
      ...team,
      kbds: ['meta', String(index + 1)],
      onSelect() {
        selectedTeam.value = team
      }
    })),
    [{ label: 'Create team', icon: CirclePlusIcon }]
  ]
})

function getItems(state: 'collapsed' | 'expanded') {
  return [
    {
      label: 'Inbox',
      icon: MailIcon,
      badge: '4'
    },
    {
      label: 'Issues',
      icon: CollabIcon
    },
    {
      label: 'Actions',
      icon: PulseCircleIcon
    },
    {
      label: 'Settings',
      icon: SettingsIcon,
      defaultOpen: true,
      children:
        state === 'expanded'
          ? [
              {
                label: 'General',
                icon: HomeIcon
              },
              {
                label: 'Team',
                icon: ThreePersonsIcon
              },
              {
                label: 'Billing',
                icon: PaymentIcon
              }
            ]
          : []
    }
  ] satisfies NavigationMenuItem[]
}

const user = ref({
  name: 'Bitrix24',
  avatar: {
    src: 'https://github.com/bitrix24.png',
    alt: 'bitrix24'
  }
})

const userItems = computed<DropdownMenuItem[][]>(() => [
  [
    {
      label: 'Profile',
      icon: UserIcon
    },
    {
      label: 'Billing',
      icon: PaymentIcon
    },
    {
      label: 'Settings',
      icon: SettingsIcon,
      to: '/templates/'
    }
  ],
  [
    {
      label: 'Appearance',
      icon: ContrastIcon,
      children: [
        {
          label: 'Light',
          icon: SunIcon,
          type: 'checkbox',
          checked: colorMode.value === 'light',
          onUpdateChecked(checked: boolean) {
            if (checked) {
              colorMode.preference = 'light'
            }
          },
          onSelect(e: Event) {
            e.preventDefault()
          }
        },
        {
          label: 'Dark',
          icon: MoonIcon,
          type: 'checkbox',
          checked: colorMode.value === 'dark',
          onUpdateChecked(checked: boolean) {
            if (checked) {
              colorMode.preference = 'dark'
            }
          },
          onSelect(e: Event) {
            e.preventDefault()
          }
        }
      ]
    }
  ],
  [
    {
      label: 'GitHub',
      icon: GitHubIcon,
      to: 'https://github.com/bitrix24/b24ui',
      target: '_blank'
    },
    {
      label: 'Log out',
      icon: LogOutIcon
    }
  ]
])

defineShortcuts(extractShortcuts(teamsItems.value))
</script>

<template>
  <div class="flex flex-1">
    <B24Sidebar
      v-model:open="open"
      collapsible="icon"
      rail
      :b24ui="{
        container: 'h-full',
        inner: 'bg-elevated/25 divide-transparent',
        body: 'py-0'
      }"
    >
      <template #header>
        <B24DropdownMenu
          :items="teamsItems"
          :content="{ align: 'start', collisionPadding: 12 }"
          :b24ui="{ content: 'w-[270px]', viewport: 'w-[270px] max-h-[62vh]' }"
        >
          <B24Button
            v-bind="selectedTeamFormatted"
            :use-dropdown="open"
            color="air-tertiary"
            class="w-full data-[state=open]:bg-(--ui-btn-background-hover) overflow-hidden"
            :b24ui="{ trailingIcon: 'text-dimmed ms-auto', label: 'flex-1' }"
          />
        </B24DropdownMenu>
      </template>

      <template #default="{ state }">
        <B24NavigationMenu
          :key="state"
          :items="getItems(state)"
          orientation="vertical"
          :b24ui="{ link: 'p-1.5 overflow-hidden' }"
        />
      </template>

      <template #footer>
        <B24DropdownMenu
          :items="userItems"
          :content="{ align: 'start', side: 'top', sideOffset: 4, collisionPadding: 12 }"
          :b24ui="{ content: 'w-[200px]', viewport: 'w-[200px] max-h-[62vh]' }"
        >
          <B24Button
            v-bind="user"
            :label="open ? user?.name : undefined"
            :use-dropdown="open"
            color="air-tertiary"
            class="w-full data-[state=open]:bg-(--ui-btn-background-hover) overflow-hidden"
            :b24ui="{ trailingIcon: 'text-dimmed ms-auto', label: 'flex-1' }"
          />
        </B24DropdownMenu>
      </template>
    </B24Sidebar>

    <div class="flex-1 flex flex-col">
      <div
        class="h-(--b24ui-header-height) shrink-0 flex items-center px-4 border-b border-default"
      >
        <B24Button
          :icon="CloseChatIcon"
          aria-label="Toggle sidebar"
          size="md"
          color="air-tertiary"
          @click="open = !open"
        />
      </div>

      <div class="flex-1 p-4">
        <Placeholder class="size-full" />
      </div>
    </div>
  </div>
</template>

Variant

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

<script setup lang="ts">
import type { NavigationMenuItem, SidebarProps } from '@bitrix24/b24ui-nuxt'
import HomeIcon from '@bitrix24/b24icons-vue/outline/HomeIcon'
import MailIcon from '@bitrix24/b24icons-vue/outline/MailIcon'
import ContactIcon from '@bitrix24/b24icons-vue/outline/ContactIcon'
import CloseChatIcon from '@bitrix24/b24icons-vue/outline/CloseChatIcon'
import OpenChatIcon from '@bitrix24/b24icons-vue/outline/OpenChatIcon'
import Bitrix24Icon from '@bitrix24/b24icons-vue/common-b24/Bitrix24Icon'
import B24Icon from '@bitrix24/b24icons-vue/main/B24Icon'

// Ignore the props for the example
defineProps<Pick<SidebarProps, 'variant' | 'collapsible' | 'side'>>()

const open = ref(true)

const items: NavigationMenuItem[] = [
  {
    label: 'Home',
    icon: HomeIcon,
    active: true
  },
  {
    label: 'Inbox',
    icon: MailIcon,
    badge: '4'
  },
  {
    label: 'Contacts',
    icon: ContactIcon
  }
]
</script>

<template>
  <div
    class="flex flex-1"
    :class="[
      variant === 'inset' &&
        'bg-(--ui-color-g-plastic-greish-bg) dark:bg-(--ui-color-base-black-fixed)',
      side === 'right' && 'flex-row-reverse'
    ]"
  >
    <B24Sidebar
      v-model:open="open"
      :variant="variant"
      :collapsible="collapsible"
      :side="side"
      :b24ui="{
        container: 'h-full'
      }"
    >
      <template #header>
        <Bitrix24Icon v-if="open" class="w-auto h-5 " />
        <B24Icon v-else class="w-auto h-8 text-[#2fc6f6]" />
      </template>

      <B24NavigationMenu
        :items="items"
        orientation="vertical"
        :b24ui="{ link: 'p-1.5 overflow-hidden' }"
      />
    </B24Sidebar>

    <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-(--b24ui-header-height) shrink-0 flex items-center px-4"
        :class="[
          variant !== 'floating' && 'border-b border-default',
          side === 'right' && 'justify-end'
        ]"
      >
        <B24Button
          :icon="side === 'right' ? OpenChatIcon : CloseChatIcon"
          color="air-tertiary"
          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 '@bitrix24/b24ui-nuxt'
import HomeIcon from '@bitrix24/b24icons-vue/outline/HomeIcon'
import MailIcon from '@bitrix24/b24icons-vue/outline/MailIcon'
import ContactIcon from '@bitrix24/b24icons-vue/outline/ContactIcon'
import CloseChatIcon from '@bitrix24/b24icons-vue/outline/CloseChatIcon'
import OpenChatIcon from '@bitrix24/b24icons-vue/outline/OpenChatIcon'
import Bitrix24Icon from '@bitrix24/b24icons-vue/common-b24/Bitrix24Icon'
import B24Icon from '@bitrix24/b24icons-vue/main/B24Icon'

// Ignore the props for the example
defineProps<Pick<SidebarProps, 'variant' | 'collapsible' | 'side'>>()

const open = ref(true)

const items: NavigationMenuItem[] = [
  {
    label: 'Home',
    icon: HomeIcon,
    active: true
  },
  {
    label: 'Inbox',
    icon: MailIcon,
    badge: '4'
  },
  {
    label: 'Contacts',
    icon: ContactIcon
  }
]
</script>

<template>
  <div
    class="flex flex-1"
    :class="[
      variant === 'inset' &&
        'bg-(--ui-color-g-plastic-greish-bg) dark:bg-(--ui-color-base-black-fixed)',
      side === 'right' && 'flex-row-reverse'
    ]"
  >
    <B24Sidebar
      v-model:open="open"
      :variant="variant"
      :collapsible="collapsible"
      :side="side"
      :b24ui="{
        container: 'h-full'
      }"
    >
      <template #header>
        <Bitrix24Icon v-if="open" class="w-auto h-5 " />
        <B24Icon v-else class="w-auto h-8 text-[#2fc6f6]" />
      </template>

      <B24NavigationMenu
        :items="items"
        orientation="vertical"
        :b24ui="{ link: 'p-1.5 overflow-hidden' }"
      />
    </B24Sidebar>

    <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-(--b24ui-header-height) shrink-0 flex items-center px-4"
        :class="[
          variant !== 'floating' && 'border-b border-default',
          side === 'right' && 'justify-end'
        ]"
      >
        <B24Button
          :icon="side === 'right' ? OpenChatIcon : CloseChatIcon"
          color="air-tertiary"
          aria-label="Toggle sidebar"
          @click="open = !open"
        />
      </div>

      <div class="flex-1 p-4">
        <Placeholder class="size-full" />
      </div>
    </div>
  </div>
</template>

Collapsible

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

  • offcanvas: The sidebar slides out of view completely.
  • icon: The sidebar shrinks to icon-only width.
  • none: The sidebar is not collapsible.
<script setup lang="ts">
import type { NavigationMenuItem, SidebarProps } from '@bitrix24/b24ui-nuxt'
import HomeIcon from '@bitrix24/b24icons-vue/outline/HomeIcon'
import MailIcon from '@bitrix24/b24icons-vue/outline/MailIcon'
import ContactIcon from '@bitrix24/b24icons-vue/outline/ContactIcon'
import CloseChatIcon from '@bitrix24/b24icons-vue/outline/CloseChatIcon'
import OpenChatIcon from '@bitrix24/b24icons-vue/outline/OpenChatIcon'
import Bitrix24Icon from '@bitrix24/b24icons-vue/common-b24/Bitrix24Icon'
import B24Icon from '@bitrix24/b24icons-vue/main/B24Icon'

// Ignore the props for the example
defineProps<Pick<SidebarProps, 'variant' | 'collapsible' | 'side'>>()

const open = ref(true)

const items: NavigationMenuItem[] = [
  {
    label: 'Home',
    icon: HomeIcon,
    active: true
  },
  {
    label: 'Inbox',
    icon: MailIcon,
    badge: '4'
  },
  {
    label: 'Contacts',
    icon: ContactIcon
  }
]
</script>

<template>
  <div
    class="flex flex-1"
    :class="[
      variant === 'inset' &&
        'bg-(--ui-color-g-plastic-greish-bg) dark:bg-(--ui-color-base-black-fixed)',
      side === 'right' && 'flex-row-reverse'
    ]"
  >
    <B24Sidebar
      v-model:open="open"
      :variant="variant"
      :collapsible="collapsible"
      :side="side"
      :b24ui="{
        container: 'h-full'
      }"
    >
      <template #header>
        <Bitrix24Icon v-if="open" class="w-auto h-5 " />
        <B24Icon v-else class="w-auto h-8 text-[#2fc6f6]" />
      </template>

      <B24NavigationMenu
        :items="items"
        orientation="vertical"
        :b24ui="{ link: 'p-1.5 overflow-hidden' }"
      />
    </B24Sidebar>

    <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-(--b24ui-header-height) shrink-0 flex items-center px-4"
        :class="[
          variant !== 'floating' && 'border-b border-default',
          side === 'right' && 'justify-end'
        ]"
      >
        <B24Button
          :icon="side === 'right' ? OpenChatIcon : CloseChatIcon"
          color="air-tertiary"
          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 '@bitrix24/b24ui-nuxt'
import HomeIcon from '@bitrix24/b24icons-vue/outline/HomeIcon'
import MailIcon from '@bitrix24/b24icons-vue/outline/MailIcon'
import ContactIcon from '@bitrix24/b24icons-vue/outline/ContactIcon'
import CloseChatIcon from '@bitrix24/b24icons-vue/outline/CloseChatIcon'
import OpenChatIcon from '@bitrix24/b24icons-vue/outline/OpenChatIcon'
import Bitrix24Icon from '@bitrix24/b24icons-vue/common-b24/Bitrix24Icon'
import B24Icon from '@bitrix24/b24icons-vue/main/B24Icon'

// Ignore the props for the example
defineProps<Pick<SidebarProps, 'variant' | 'collapsible' | 'side'>>()

const open = ref(true)

const items: NavigationMenuItem[] = [
  {
    label: 'Home',
    icon: HomeIcon,
    active: true
  },
  {
    label: 'Inbox',
    icon: MailIcon,
    badge: '4'
  },
  {
    label: 'Contacts',
    icon: ContactIcon
  }
]
</script>

<template>
  <div
    class="flex flex-1"
    :class="[
      variant === 'inset' &&
        'bg-(--ui-color-g-plastic-greish-bg) dark:bg-(--ui-color-base-black-fixed)',
      side === 'right' && 'flex-row-reverse'
    ]"
  >
    <B24Sidebar
      v-model:open="open"
      :variant="variant"
      :collapsible="collapsible"
      :side="side"
      :b24ui="{
        container: 'h-full'
      }"
    >
      <template #header>
        <Bitrix24Icon v-if="open" class="w-auto h-5 " />
        <B24Icon v-else class="w-auto h-8 text-[#2fc6f6]" />
      </template>

      <B24NavigationMenu
        :items="items"
        orientation="vertical"
        :b24ui="{ link: 'p-1.5 overflow-hidden' }"
      />
    </B24Sidebar>

    <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-(--b24ui-header-height) shrink-0 flex items-center px-4"
        :class="[
          variant !== 'floating' && 'border-b border-default',
          side === 'right' && 'justify-end'
        ]"
      >
        <B24Button
          :icon="side === 'right' ? OpenChatIcon : CloseChatIcon"
          color="air-tertiary"
          aria-label="Toggle sidebar"
          @click="open = !open"
        />
      </div>

      <div class="flex-1 p-4">
        <Placeholder class="size-full" />
      </div>
    </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.

<script setup lang="ts">
import type { NavigationMenuItem, SidebarProps } from '@bitrix24/b24ui-nuxt'
import HomeIcon from '@bitrix24/b24icons-vue/outline/HomeIcon'
import MailIcon from '@bitrix24/b24icons-vue/outline/MailIcon'
import ContactIcon from '@bitrix24/b24icons-vue/outline/ContactIcon'
import CloseChatIcon from '@bitrix24/b24icons-vue/outline/CloseChatIcon'
import OpenChatIcon from '@bitrix24/b24icons-vue/outline/OpenChatIcon'
import Bitrix24Icon from '@bitrix24/b24icons-vue/common-b24/Bitrix24Icon'
import B24Icon from '@bitrix24/b24icons-vue/main/B24Icon'

// Ignore the props for the example
defineProps<Pick<SidebarProps, 'variant' | 'collapsible' | 'side'>>()

const open = ref(true)

const items: NavigationMenuItem[] = [
  {
    label: 'Home',
    icon: HomeIcon,
    active: true
  },
  {
    label: 'Inbox',
    icon: MailIcon,
    badge: '4'
  },
  {
    label: 'Contacts',
    icon: ContactIcon
  }
]
</script>

<template>
  <div
    class="flex flex-1"
    :class="[
      variant === 'inset' &&
        'bg-(--ui-color-g-plastic-greish-bg) dark:bg-(--ui-color-base-black-fixed)',
      side === 'right' && 'flex-row-reverse'
    ]"
  >
    <B24Sidebar
      v-model:open="open"
      :variant="variant"
      :collapsible="collapsible"
      :side="side"
      :b24ui="{
        container: 'h-full'
      }"
    >
      <template #header>
        <Bitrix24Icon v-if="open" class="w-auto h-5 " />
        <B24Icon v-else class="w-auto h-8 text-[#2fc6f6]" />
      </template>

      <B24NavigationMenu
        :items="items"
        orientation="vertical"
        :b24ui="{ link: 'p-1.5 overflow-hidden' }"
      />
    </B24Sidebar>

    <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-(--b24ui-header-height) shrink-0 flex items-center px-4"
        :class="[
          variant !== 'floating' && 'border-b border-default',
          side === 'right' && 'justify-end'
        ]"
      >
        <B24Button
          :icon="side === 'right' ? OpenChatIcon : CloseChatIcon"
          color="air-tertiary"
          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 '@bitrix24/b24ui-nuxt'
import HomeIcon from '@bitrix24/b24icons-vue/outline/HomeIcon'
import MailIcon from '@bitrix24/b24icons-vue/outline/MailIcon'
import ContactIcon from '@bitrix24/b24icons-vue/outline/ContactIcon'
import CloseChatIcon from '@bitrix24/b24icons-vue/outline/CloseChatIcon'
import OpenChatIcon from '@bitrix24/b24icons-vue/outline/OpenChatIcon'
import Bitrix24Icon from '@bitrix24/b24icons-vue/common-b24/Bitrix24Icon'
import B24Icon from '@bitrix24/b24icons-vue/main/B24Icon'

// Ignore the props for the example
defineProps<Pick<SidebarProps, 'variant' | 'collapsible' | 'side'>>()

const open = ref(true)

const items: NavigationMenuItem[] = [
  {
    label: 'Home',
    icon: HomeIcon,
    active: true
  },
  {
    label: 'Inbox',
    icon: MailIcon,
    badge: '4'
  },
  {
    label: 'Contacts',
    icon: ContactIcon
  }
]
</script>

<template>
  <div
    class="flex flex-1"
    :class="[
      variant === 'inset' &&
        'bg-(--ui-color-g-plastic-greish-bg) dark:bg-(--ui-color-base-black-fixed)',
      side === 'right' && 'flex-row-reverse'
    ]"
  >
    <B24Sidebar
      v-model:open="open"
      :variant="variant"
      :collapsible="collapsible"
      :side="side"
      :b24ui="{
        container: 'h-full'
      }"
    >
      <template #header>
        <Bitrix24Icon v-if="open" class="w-auto h-5 " />
        <B24Icon v-else class="w-auto h-8 text-[#2fc6f6]" />
      </template>

      <B24NavigationMenu
        :items="items"
        orientation="vertical"
        :b24ui="{ link: 'p-1.5 overflow-hidden' }"
      />
    </B24Sidebar>

    <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-(--b24ui-header-height) shrink-0 flex items-center px-4"
        :class="[
          variant !== 'floating' && 'border-b border-default',
          side === 'right' && 'justify-end'
        ]"
      >
        <B24Button
          :icon="side === 'right' ? OpenChatIcon : CloseChatIcon"
          color="air-tertiary"
          aria-label="Toggle sidebar"
          @click="open = !open"
        />
      </div>

      <div class="flex-1 p-4">
        <Placeholder class="size-full" />
      </div>
    </div>
  </div>
</template>

Title

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

<template>
  <B24Sidebar title="Navigation">
    <Placeholder class="h-full" />
  </B24Sidebar>
</template>

Description

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

<template>
  <B24Sidebar title="Navigation" description="Browse your workspace">
    <Placeholder class="h-full" />
  </B24Sidebar>
</template>

Rail

Use the rail prop to display a thin interactive edge on the sidebar that toggles the collapsed state on click. The rail is only rendered when collapsible is not none.

<template>
  <B24Sidebar rail collapsible="icon" title="Navigation">
    <Placeholder class="h-full" />
  </B24Sidebar>
</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.

<template>
  <B24Sidebar close rail collapsible="icon" title="Navigation">
    <Placeholder class="h-full" />
  </B24Sidebar>
</template>

Close Icon

Use the close-icon prop to customize the close button Icon.

<script setup lang="ts">
import RocketIcon from '@bitrix24/b24icons-vue/main/RocketIcon'
</script>

<template>
  <B24Sidebar
    close
    :close-icon="RocketIcon"
    rail
    collapsible="icon"
    side="right"
    title="Navigation"
  >
    <Placeholder class="h-full" />
  </B24Sidebar>
</template>

Mode

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

<script setup lang="ts">
import type { NavigationMenuItem, SidebarProps } from '@bitrix24/b24ui-nuxt'
import HomeIcon from '@bitrix24/b24icons-vue/outline/HomeIcon'
import MailIcon from '@bitrix24/b24icons-vue/outline/MailIcon'
import ContactIcon from '@bitrix24/b24icons-vue/outline/ContactIcon'
import OpenChatIcon from '@bitrix24/b24icons-vue/outline/OpenChatIcon'

// Ignore the props for the example
defineProps<Pick<SidebarProps, 'mode'>>()

const open = ref(true)

const items: NavigationMenuItem[] = [
  {
    label: 'Home',
    icon: HomeIcon,
    active: true
  },
  {
    label: 'Inbox',
    icon: MailIcon,
    badge: '4'
  },
  {
    label: 'Contacts',
    icon: ContactIcon
  }
]
</script>

<template>
  <div class="flex flex-1">
    <B24Sidebar v-model:open="open" :mode="mode" title="Navigation">
      <B24NavigationMenu
        :items="items"
        orientation="vertical"
        :b24ui="{ link: 'p-1.5 overflow-hidden' }"
      />
    </B24Sidebar>

    <div class="flex-1 flex flex-col">
      <div class="h-(--b24ui-header-height) shrink-0 flex items-center px-4 border-b border-default">
        <B24Button
          :icon="OpenChatIcon"
          color="air-tertiary"
          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 '@bitrix24/b24ui-nuxt'
import HomeIcon from '@bitrix24/b24icons-vue/outline/HomeIcon'
import MailIcon from '@bitrix24/b24icons-vue/outline/MailIcon'
import ContactIcon from '@bitrix24/b24icons-vue/outline/ContactIcon'
import OpenChatIcon from '@bitrix24/b24icons-vue/outline/OpenChatIcon'

// Ignore the props for the example
defineProps<Pick<SidebarProps, 'mode'>>()

const open = ref(true)

const items: NavigationMenuItem[] = [
  {
    label: 'Home',
    icon: HomeIcon,
    active: true
  },
  {
    label: 'Inbox',
    icon: MailIcon,
    badge: '4'
  },
  {
    label: 'Contacts',
    icon: ContactIcon
  }
]
</script>

<template>
  <div class="flex flex-1">
    <B24Sidebar v-model:open="open" :mode="mode" title="Navigation">
      <B24NavigationMenu
        :items="items"
        orientation="vertical"
        :b24ui="{ link: 'p-1.5 overflow-hidden' }"
      />
    </B24Sidebar>

    <div class="flex-1 flex flex-col">
      <div class="h-(--b24ui-header-height) shrink-0 flex items-center px-4 border-b border-default">
        <B24Button
          :icon="OpenChatIcon"
          color="air-tertiary"
          aria-label="Toggle sidebar"
          @click="open = !open"
        />
      </div>

      <div class="flex-1 p-4">
        <Placeholder class="size-full" />
      </div>
    </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.

<script setup lang="ts">
import type { NavigationMenuItem } from '@bitrix24/b24ui-nuxt'
import HomeIcon from '@bitrix24/b24icons-vue/outline/HomeIcon'
import MailIcon from '@bitrix24/b24icons-vue/outline/MailIcon'
import ContactIcon from '@bitrix24/b24icons-vue/outline/ContactIcon'
import OpenChatIcon from '@bitrix24/b24icons-vue/outline/OpenChatIcon'

const open = ref(true)

defineShortcuts({
  o: () => (open.value = !open.value)
})

const items: NavigationMenuItem[] = [
  {
    label: 'Home',
    icon: HomeIcon,
    active: true
  },
  {
    label: 'Inbox',
    icon: MailIcon,
    badge: '4'
  },
  {
    label: 'Contacts',
    icon: ContactIcon
  }
]
</script>

<template>
  <div class="flex flex-1">
    <B24Sidebar v-model:open="open" title="Navigation" collapsible="icon">
      <B24NavigationMenu
        :items="items"
        orientation="vertical"
        :b24ui="{ link: 'p-1.5 overflow-hidden' }"
      />
    </B24Sidebar>

    <div class="flex-1 flex flex-col">
      <div
        class="h-(--b24ui-header-height) shrink-0 flex items-center px-4 border-b border-default"
      >
        <B24Button
          :icon="OpenChatIcon"
          color="air-tertiary"
          :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 '@bitrix24/b24ui-nuxt'
import HomeIcon from '@bitrix24/b24icons-vue/outline/HomeIcon'
import MailIcon from '@bitrix24/b24icons-vue/outline/MailIcon'
import ContactIcon from '@bitrix24/b24icons-vue/outline/ContactIcon'
import OpenChatIcon from '@bitrix24/b24icons-vue/outline/OpenChatIcon'

const open = ref(true)

defineShortcuts({
  o: () => (open.value = !open.value)
})

const items: NavigationMenuItem[] = [
  {
    label: 'Home',
    icon: HomeIcon,
    active: true
  },
  {
    label: 'Inbox',
    icon: MailIcon,
    badge: '4'
  },
  {
    label: 'Contacts',
    icon: ContactIcon
  }
]
</script>

<template>
  <div class="flex flex-1">
    <B24Sidebar v-model:open="open" title="Navigation" collapsible="icon">
      <B24NavigationMenu
        :items="items"
        orientation="vertical"
        :b24ui="{ link: 'p-1.5 overflow-hidden' }"
      />
    </B24Sidebar>

    <div class="flex-1 flex flex-col">
      <div
        class="h-(--b24ui-header-height) shrink-0 flex items-center px-4 border-b border-default"
      >
        <B24Button
          :icon="OpenChatIcon"
          color="air-tertiary"
          :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>
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.

<script setup lang="ts">
import type { NavigationMenuItem } from '@bitrix24/b24ui-nuxt'
import { useLocalStorage } from '@vueuse/core'
import HomeIcon from '@bitrix24/b24icons-vue/outline/HomeIcon'
import MailIcon from '@bitrix24/b24icons-vue/outline/MailIcon'
import ContactIcon from '@bitrix24/b24icons-vue/outline/ContactIcon'
import OpenChatIcon from '@bitrix24/b24icons-vue/outline/OpenChatIcon'

const open = useLocalStorage('sidebar-open', true)

defineShortcuts({
  o: () => (open.value = !open.value)
})

const items: NavigationMenuItem[] = [
  {
    label: 'Home',
    icon: HomeIcon,
    active: true
  },
  {
    label: 'Inbox',
    icon: MailIcon,
    badge: '4'
  },
  {
    label: 'Contacts',
    icon: ContactIcon
  }
]
</script>

<template>
  <div class="flex flex-1">
    <B24Sidebar v-model:open="open" title="Navigation" collapsible="icon">
      <B24NavigationMenu
        :items="items"
        orientation="vertical"
        :b24ui="{ link: 'p-1.5 overflow-hidden' }"
      />
    </B24Sidebar>

    <div class="flex-1 flex flex-col">
      <div
        class="h-(--b24ui-header-height) shrink-0 flex items-center px-4 border-b border-default"
      >
        <B24Button
          :icon="OpenChatIcon"
          color="air-tertiary"
          aria-label="Toggle sidebar"
          @click="open = !open"
        />
      </div>

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

With custom width

The sidebar width is controlled by the --sidebar-width CSS variable (defaults to 16rem). 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.

<script setup lang="ts">
import type { NavigationMenuItem } from '@bitrix24/b24ui-nuxt'
import HomeIcon from '@bitrix24/b24icons-vue/outline/HomeIcon'
import MailIcon from '@bitrix24/b24icons-vue/outline/MailIcon'
import ContactIcon from '@bitrix24/b24icons-vue/outline/ContactIcon'
import OpenChatIcon from '@bitrix24/b24icons-vue/outline/OpenChatIcon'

const open = ref(true)

const items: NavigationMenuItem[] = [
  {
    label: 'Home',
    icon: HomeIcon,
    active: true
  },
  {
    label: 'Inbox',
    icon: MailIcon,
    badge: '4'
  },
  {
    label: 'Contacts',
    icon: ContactIcon
  }
]
</script>

<template>
  <div class="flex flex-1">
    <B24Sidebar v-model:open="open" collapsible="icon" :style="{ '--sidebar-width': '20rem' }">
      <B24NavigationMenu
        :items="items"
        orientation="vertical"
        :b24ui="{ link: 'p-1.5 overflow-hidden' }"
      />
    </B24Sidebar>

    <div class="flex-1 flex flex-col">
      <div
        class="h-(--b24ui-header-height) shrink-0 flex items-center px-4 border-b border-default"
      >
        <B24Button
          :icon="OpenChatIcon"
          color="air-tertiary"
          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 '@bitrix24/b24ui-nuxt'
import HomeIcon from '@bitrix24/b24icons-vue/outline/HomeIcon'
import MailIcon from '@bitrix24/b24icons-vue/outline/MailIcon'
import ContactIcon from '@bitrix24/b24icons-vue/outline/ContactIcon'
import OpenChatIcon from '@bitrix24/b24icons-vue/outline/OpenChatIcon'

const open = ref(true)

const items: NavigationMenuItem[] = [
  {
    label: 'Home',
    icon: HomeIcon,
    active: true
  },
  {
    label: 'Inbox',
    icon: MailIcon,
    badge: '4'
  },
  {
    label: 'Contacts',
    icon: ContactIcon
  }
]
</script>

<template>
  <div class="flex flex-1">
    <B24Sidebar v-model:open="open" collapsible="icon" :style="{ '--sidebar-width': '20rem' }">
      <B24NavigationMenu
        :items="items"
        orientation="vertical"
        :b24ui="{ link: 'p-1.5 overflow-hidden' }"
      />
    </B24Sidebar>

    <div class="flex-1 flex flex-col">
      <div
        class="h-(--b24ui-header-height) shrink-0 flex items-center px-4 border-b border-default"
      >
        <B24Button
          :icon="OpenChatIcon"
          color="air-tertiary"
          aria-label="Toggle sidebar"
          @click="open = !open"
        />
      </div>

      <div class="flex-1 p-4">
        <Placeholder class="size-full" />
      </div>
    </div>
  </div>
</template>

With header

To position the sidebar below a Header, customize the gap and container using the b24ui prop.

<script setup lang="ts">
import type { NavigationMenuItem } from '@bitrix24/b24ui-nuxt'
import HomeIcon from '@bitrix24/b24icons-vue/outline/HomeIcon'
import MailIcon from '@bitrix24/b24icons-vue/outline/MailIcon'
import ContactIcon from '@bitrix24/b24icons-vue/outline/ContactIcon'
import HamburgerMenuIcon from '@bitrix24/b24icons-vue/outline/HamburgerMenuIcon'

const open = ref(true)

const items: NavigationMenuItem[] = [
  {
    label: 'Home',
    icon: HomeIcon,
    active: true
  },
  {
    label: 'Inbox',
    icon: MailIcon,
    badge: '4'
  },
  {
    label: 'Contacts',
    icon: ContactIcon
  }
]
</script>

<template>
  <div class="flex flex-col flex-1">
    <B24Header title="Title" toggle-side="left" :b24ui="{ container: 'px-4!' }">
      <template #toggle>
        <B24Button
          :icon="HamburgerMenuIcon"
          color="air-tertiary"
          aria-label="Toggle sidebar"
          @click="open = !open"
        />
      </template>
    </B24Header>

    <div class="flex flex-1 min-h-0">
      <B24Sidebar
        v-model:open="open"
        collapsible="icon"
        :b24ui="{
          gap: 'h-[calc(100%-var(--b24ui-header-height))]',
          container:
            'absolute top-(--b24ui-header-height) bottom-0 h-[calc(100%-var(--b24ui-header-height))]'
        }"
      >
        <B24NavigationMenu
          :items="items"
          orientation="vertical"
          :b24ui="{ link: 'p-1.5 overflow-hidden' }"
        />
      </B24Sidebar>

      <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 '@bitrix24/b24ui-nuxt'
import HomeIcon from '@bitrix24/b24icons-vue/outline/HomeIcon'
import MailIcon from '@bitrix24/b24icons-vue/outline/MailIcon'
import ContactIcon from '@bitrix24/b24icons-vue/outline/ContactIcon'
import HamburgerMenuIcon from '@bitrix24/b24icons-vue/outline/HamburgerMenuIcon'

const open = ref(true)

const items: NavigationMenuItem[] = [
  {
    label: 'Home',
    icon: HomeIcon,
    active: true
  },
  {
    label: 'Inbox',
    icon: MailIcon,
    badge: '4'
  },
  {
    label: 'Contacts',
    icon: ContactIcon
  }
]
</script>

<template>
  <div class="flex flex-col flex-1">
    <B24Header title="Title" toggle-side="left" :b24ui="{ container: 'px-4!' }">
      <template #toggle>
        <B24Button
          :icon="HamburgerMenuIcon"
          color="air-tertiary"
          aria-label="Toggle sidebar"
          @click="open = !open"
        />
      </template>
    </B24Header>

    <div class="flex flex-1 min-h-0">
      <B24Sidebar
        v-model:open="open"
        collapsible="icon"
        :b24ui="{
          gap: 'h-[calc(100%-var(--b24ui-header-height))]',
          container:
            'absolute top-(--b24ui-header-height) bottom-0 h-[calc(100%-var(--b24ui-header-height))]'
        }"
      >
        <B24NavigationMenu
          :items="items"
          orientation="vertical"
          :b24ui="{ link: 'p-1.5 overflow-hidden' }"
        />
      </B24Sidebar>

      <div class="flex-1 p-4">
        <Placeholder class="size-full" />
      </div>
    </div>
  </div>
</template>
The --b24ui-header-height variable defaults to 3.625rem and is used by the Header. Adjust it if your navbar uses a different height.

With AI chat

Use the sidebar on the right side with ChatMessages and ChatPrompt to create an AI chat panel.

<script setup lang="ts">
import type { UIMessage } from 'ai'
import { DefaultChatTransport, isTextUIPart } from 'ai'
import { Chat } from '@ai-sdk/vue'
import OpenChatIcon from '@bitrix24/b24icons-vue/outline/OpenChatIcon'

const config = useRuntimeConfig()
const open = ref(true)
const input = ref('')

const messages: UIMessage[] = [
  {
    id: '1',
    role: 'user',
    parts: [{ type: 'text', text: 'What is Bitrix24 UI?' }]
  },
  {
    id: '2',
    role: 'assistant',
    parts: [
      {
        type: 'text',
        text: 'Bitrix24 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 chat = new Chat({
  messages,
  transport: new DefaultChatTransport({
    api: `${config.public.baseUrl}/api/chat`
  })
})

function onSubmit() {
  if (!input.value.trim()) return

  chat.sendMessage({ text: input.value })

  input.value = ''
}

const b24ui = {
  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-(--b24ui-header-height) shrink-0 flex items-center justify-end px-4 border-b border-default"
      >
        <B24Button
          :icon="OpenChatIcon"
          color="air-tertiary"
          aria-label="Toggle sidebar"
          @click="open = !open"
        />
      </div>

      <div class="flex-1 p-4">
        <Placeholder class="size-full" />
      </div>
    </div>

    <B24Sidebar
      v-model:open="open"
      side="right"
      title="AI Chat"
      close
      :style="{ '--sidebar-width': '20rem' }"
      :b24ui="{ container: 'h-full' }"
    >
      <B24Theme :b24ui="b24ui">
        <B24ChatMessages :messages="chat.messages" :status="chat.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)">
                <MDC
                  v-if="message.role === 'assistant'"
                  :value="part.text"
                  :cache-key="`${message.id}-${index}`"
                  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>
        </B24ChatMessages>
      </B24Theme>

      <template #footer>
        <B24ChatPrompt
          v-model="input"
          :error="chat.error"
          :autofocus="false"
          variant="outline"
          size="sm"
          :b24ui="{ base: 'px-0' }"
          @submit="onSubmit"
        >
          <B24ChatPromptSubmit
            size="sm"
            :status="chat.status"
            @stop="chat.stop()"
            @reload="chat.regenerate()"
          />
        </B24ChatPrompt>
      </template>
    </B24Sidebar>
  </div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { UIMessage } from 'ai'
import { DefaultChatTransport, isTextUIPart } from 'ai'
import { Chat } from '@ai-sdk/vue'
import OpenChatIcon from '@bitrix24/b24icons-vue/outline/OpenChatIcon'

const config = useRuntimeConfig()
const open = ref(true)
const input = ref('')

const messages: UIMessage[] = [
  {
    id: '1',
    role: 'user',
    parts: [{ type: 'text', text: 'What is Bitrix24 UI?' }]
  },
  {
    id: '2',
    role: 'assistant',
    parts: [
      {
        type: 'text',
        text: 'Bitrix24 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 chat = new Chat({
  messages,
  transport: new DefaultChatTransport({
    api: `${config.public.baseUrl}/api/chat`
  })
})

function onSubmit() {
  if (!input.value.trim()) return

  chat.sendMessage({ text: input.value })

  input.value = ''
}

const b24ui = {
  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-(--b24ui-header-height) shrink-0 flex items-center justify-end px-4 border-b border-default"
      >
        <B24Button
          :icon="OpenChatIcon"
          color="air-tertiary"
          aria-label="Toggle sidebar"
          @click="open = !open"
        />
      </div>

      <div class="flex-1 p-4">
        <Placeholder class="size-full" />
      </div>
    </div>

    <B24Sidebar
      v-model:open="open"
      side="right"
      title="AI Chat"
      close
      :style="{ '--sidebar-width': '20rem' }"
      :b24ui="{ container: 'h-full' }"
    >
      <B24Theme :b24ui="b24ui">
        <B24ChatMessages :messages="chat.messages" :status="chat.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)">
                <MDC
                  v-if="message.role === 'assistant'"
                  :value="part.text"
                  :cache-key="`${message.id}-${index}`"
                  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>
        </B24ChatMessages>
      </B24Theme>

      <template #footer>
        <B24ChatPrompt
          v-model="input"
          :error="chat.error"
          :autofocus="false"
          variant="outline"
          size="sm"
          :b24ui="{ base: 'px-0' }"
          @submit="onSubmit"
        >
          <B24ChatPromptSubmit
            size="sm"
            :status="chat.status"
            @stop="chat.stop()"
            @reload="chat.regenerate()"
          />
        </B24ChatPrompt>
      </template>
    </B24Sidebar>
  </div>
</template>

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'offcanvas' "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' }

closeIconicons.closeIconComponent

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
b24ui { root?: ClassNameValue; gap?: ClassNameValue; container?: ClassNameValue; inner?: ClassNameValue; header?: ClassNameValue; wrapper?: ClassNameValue; title?: ClassNameValue; description?: ClassNameValue; actions?: ClassNameValue; close?: ClassNameValue; body?: ClassNameValue; footer?: ClassNameValue; rail?: ClassNameValue; content?: ClassNameValue; overlay?: ClassNameValue; }

Slots

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

Theme

https://github.com/bitrix24/b24ui/tree/main/src/theme/sidebar.ts
export default {
  slots: {
    root: 'peer [--sidebar-width:15rem] [--sidebar-width-icon:4.5rem]',
    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 divide-y divide-default',
    header: 'flex items-center gap-1.5 overflow-hidden px-4 min-h-(--b24ui-header-height)',
    wrapper: 'min-w-0 flex-1',
    title: 'text-label 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 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-px lg:flex hover:after:bg-(--ui-color-divider-accent) after:transition-colors',
    content: '',
    overlay: ''
  },
  variants: {
    side: {
      left: {
        container: 'left-0 border-e border-default',
        rail: 'end-0 translate-x-1/2'
      },
      right: {
        container: 'right-0 border-s border-default',
        rail: '-start-px -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 data-[state=collapsed]:cursor-e-resize'
      }
    },
    {
      side: 'right',
      collapsible: [
        'offcanvas',
        'icon'
      ],
      class: {
        rail: 'cursor-e-resize data-[state=collapsed]:cursor-w-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)+--spacing(6.5))]',
        container: 'data-[state=collapsed]:w-[calc(var(--sidebar-width-icon)+--spacing(6.5)+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)]'
      }
    }
  ]
}