v2.5.3

Tabs

A collection of tab panels shown individually.

Usage

Use the Tabs component to display a list of items in a tabs.

<script setup lang="ts">
import type { TabsItem } from '@bitrix24/b24ui-nuxt'
import UserIcon from '@bitrix24/b24icons-vue/common-b24/UserIcon'
import Shield2ContourIcon from '@bitrix24/b24icons-vue/main/Shield2ContourIcon'

const items = [
  {
    label: 'Account',
    icon: UserIcon,
    slot: 'account'
  },
  {
    label: 'Password',
    icon: Shield2ContourIcon,
    slot: 'password'
  }
] satisfies TabsItem[]

const state = reactive({
  name: 'System User',
  username: 'system-user',
  currentPassword: '',
  newPassword: '',
  confirmPassword: ''
})
</script>

<template>
  <B24Tabs :items="items">
    <template #account>
      <B24Form :state="state" class="flex flex-col gap-4">
        <B24FormField label="Name" name="name">
          <B24Input v-model="state.name" class="w-full" />
        </B24FormField>
        <B24FormField label="Username" name="username">
          <B24Input v-model="state.username" class="w-full" />
        </B24FormField>
      </B24Form>
    </template>

    <template #password>
      <B24Form :state="state" class="flex flex-col gap-4">
        <B24FormField label="Current Password" name="current" required>
          <B24Input v-model="state.currentPassword" type="password" required class="w-full" />
        </B24FormField>
        <B24FormField label="New Password" name="new" required>
          <B24Input v-model="state.newPassword" type="password" required class="w-full" />
        </B24FormField>
        <B24FormField label="Confirm Password" name="confirm" required>
          <B24Input v-model="state.confirmPassword" type="password" required class="w-full" />
        </B24FormField>
      </B24Form>
    </template>
  </B24Tabs>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
import type { TabsItem } from '@bitrix24/b24ui-nuxt'
import UserIcon from '@bitrix24/b24icons-vue/common-b24/UserIcon'
import Shield2ContourIcon from '@bitrix24/b24icons-vue/main/Shield2ContourIcon'

const items = [
  {
    label: 'Account',
    icon: UserIcon,
    slot: 'account'
  },
  {
    label: 'Password',
    icon: Shield2ContourIcon,
    slot: 'password'
  }
] satisfies TabsItem[]

const state = reactive({
  name: 'System User',
  username: 'system-user',
  currentPassword: '',
  newPassword: '',
  confirmPassword: ''
})
</script>

<template>
  <B24Tabs :items="items">
    <template #account>
      <B24Form :state="state" class="flex flex-col gap-4">
        <B24FormField label="Name" name="name">
          <B24Input v-model="state.name" class="w-full" />
        </B24FormField>
        <B24FormField label="Username" name="username">
          <B24Input v-model="state.username" class="w-full" />
        </B24FormField>
      </B24Form>
    </template>

    <template #password>
      <B24Form :state="state" class="flex flex-col gap-4">
        <B24FormField label="Current Password" name="current" required>
          <B24Input v-model="state.currentPassword" type="password" required class="w-full" />
        </B24FormField>
        <B24FormField label="New Password" name="new" required>
          <B24Input v-model="state.newPassword" type="password" required class="w-full" />
        </B24FormField>
        <B24FormField label="Confirm Password" name="confirm" required>
          <B24Input v-model="state.confirmPassword" type="password" required class="w-full" />
        </B24FormField>
      </B24Form>
    </template>
  </B24Tabs>
</template>

Items

Use the items prop as an array of objects with the following properties:

  • label?: string
  • icon?: IconComponent
  • avatar?: AvatarProps
  • badge?: string | number | BadgeProps
  • content?: string
  • value?: string | number
  • disabled?: boolean
  • slot?: string
  • class?: any
  • b24ui?: { trigger?: ClassNameValue, leadingIcon?: ClassNameValue, leadingAvatar?: ClassNameValue, leadingAvatarSize?: ClassNameValue, label?: ClassNameValue, trailingBadge?: ClassNameValue, trailingBadgeSize?: ClassNameValue, content?: ClassNameValue }
This is the account content.
<script setup lang="ts">
import type { TabsItem } from '@bitrix24/b24ui-nuxt'

const items = ref<TabsItem[]>([
  {
    label: 'Account',
    content: 'This is the account content.'
  },
  {
    label: 'Password',
    content: 'This is the password content.'
  }
])
</script>

<template>
  <B24Tabs :items="items" class="w-full" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { TabsItem } from '@bitrix24/b24ui-nuxt'

const items = ref<TabsItem[]>([
  {
    label: 'Account',
    content: 'This is the account content.'
  },
  {
    label: 'Password',
    content: 'This is the password content.'
  }
])
</script>

<template>
  <B24Tabs :items="items" class="w-full" />
</template>

Content

Set the content prop to false to turn the Tabs into a toggle-only control without displaying any content. Defaults to true.

<script setup lang="ts">
import type { TabsItem } from '@bitrix24/b24ui-nuxt'

const items = ref<TabsItem[]>([
  {
    label: 'Account',
    content: 'This is the account content.'
  },
  {
    label: 'Password',
    content: 'This is the password content.'
  }
])
</script>

<template>
  <B24Tabs :content="false" :items="items" class="w-full" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { TabsItem } from '@bitrix24/b24ui-nuxt'

const items = ref<TabsItem[]>([
  {
    label: 'Account',
    content: 'This is the account content.'
  },
  {
    label: 'Password',
    content: 'This is the password content.'
  }
])
</script>

<template>
  <B24Tabs :content="false" :items="items" class="w-full" />
</template>

Unmount

Use the unmount-on-hide prop to prevent the content from being unmounted when the Tabs is collapsed. Defaults to true.

This is the account content.
<script setup lang="ts">
import type { TabsItem } from '@bitrix24/b24ui-nuxt'

const items = ref<TabsItem[]>([
  {
    label: 'Account',
    content: 'This is the account content.'
  },
  {
    label: 'Password',
    content: 'This is the password content.'
  }
])
</script>

<template>
  <B24Tabs :unmount-on-hide="false" :items="items" class="w-full" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { TabsItem } from '@bitrix24/b24ui-nuxt'

const items = ref<TabsItem[]>([
  {
    label: 'Account',
    content: 'This is the account content.'
  },
  {
    label: 'Password',
    content: 'This is the password content.'
  }
])
</script>

<template>
  <B24Tabs :unmount-on-hide="false" :items="items" class="w-full" />
</template>
You can inspect the DOM to see each item's content being rendered.

Size

Use the size prop to change the size of the Tabs.

<script setup lang="ts">
import type { TabsItem } from '@bitrix24/b24ui-nuxt'

const items = ref<TabsItem[]>([
  {
    label: 'Account'
  },
  {
    label: 'Password'
  }
])
</script>

<template>
  <B24Tabs size="md" :content="false" :items="items" class="w-full" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { TabsItem } from '@bitrix24/b24ui-nuxt'

const items = ref<TabsItem[]>([
  {
    label: 'Account'
  },
  {
    label: 'Password'
  }
])
</script>

<template>
  <B24Tabs size="md" :content="false" :items="items" class="w-full" />
</template>

Orientation

Use the orientation prop to change the orientation of the Tabs. Defaults to horizontal.

<script setup lang="ts">
import type { TabsItem } from '@bitrix24/b24ui-nuxt'

const items = ref<TabsItem[]>([
  {
    label: 'Account'
  },
  {
    label: 'Password'
  }
])
</script>

<template>
  <B24Tabs orientation="vertical" :content="false" :items="items" class="w-full" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { TabsItem } from '@bitrix24/b24ui-nuxt'

const items = ref<TabsItem[]>([
  {
    label: 'Account'
  },
  {
    label: 'Password'
  }
])
</script>

<template>
  <B24Tabs orientation="vertical" :content="false" :items="items" class="w-full" />
</template>

Examples

Control active item

You can control the active item by using the default-value prop or the v-model directive with the value of the item. If no value is provided, it defaults to the index as a string.

<script setup lang="ts">
import type { TabsItem } from '@bitrix24/b24ui-nuxt'
import UserIcon from '@bitrix24/b24icons-vue/common-b24/UserIcon'
import Shield2ContourIcon from '@bitrix24/b24icons-vue/main/Shield2ContourIcon'

const items: TabsItem[] = [
  {
    label: 'Account',
    icon: UserIcon
  },
  {
    label: 'Password',
    icon: Shield2ContourIcon
  }
]

const active = ref('0')

// Note: This is for demonstration purposes only. Don't do this at home.
onMounted(() => {
  setInterval(() => {
    active.value = String((Number(active.value) + 1) % items.length)
  }, 2000)
})
</script>

<template>
  <B24Tabs v-model="active" :content="false" :items="items" class="w-full" />
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import type { TabsItem } from '@bitrix24/b24ui-nuxt'
import UserIcon from '@bitrix24/b24icons-vue/common-b24/UserIcon'
import Shield2ContourIcon from '@bitrix24/b24icons-vue/main/Shield2ContourIcon'

const items: TabsItem[] = [
  {
    label: 'Account',
    icon: UserIcon
  },
  {
    label: 'Password',
    icon: Shield2ContourIcon
  }
]

const active = ref('0')

// Note: This is for demonstration purposes only. Don't do this at home.
onMounted(() => {
  setInterval(() => {
    active.value = String((Number(active.value) + 1) % items.length)
  }, 2000)
})
</script>

<template>
  <B24Tabs v-model="active" :content="false" :items="items" class="w-full" />
</template>
Use the value-key prop to change the key used to match items when a v-model or default-value is provided.

With route query

You can control the active item by a URL query parameter, using route.query.tab as the value of the item.

<script setup lang="ts">
import type { TabsItem } from '@bitrix24/b24ui-nuxt'
import UserIcon from '@bitrix24/b24icons-vue/common-b24/UserIcon'
import Shield2ContourIcon from '@bitrix24/b24icons-vue/main/Shield2ContourIcon'

const route = useRoute()
const router = useRouter()

const items: TabsItem[] = [
  {
    label: 'Account',
    icon: UserIcon,
    value: 'account'
  },
  {
    label: 'Password',
    icon: Shield2ContourIcon,
    value: 'password'
  }
]

const active = computed({
  get() {
    return (route.query.tab as string) || 'account'
  },
  set(tab) {
    // Hash is specified here to prevent the page from scrolling to the top
    router.push({
      path: '/b24ui/docs/components/tabs',
      query: { tab },
      hash: '#with-route-query'
    })
  }
})
</script>

<template>
  <B24Tabs v-model="active" :content="false" :items="items" class="w-full" />
</template>
<script setup lang="ts">
import { computed } from 'vue'
import type { TabsItem } from '@bitrix24/b24ui-nuxt'
import UserIcon from '@bitrix24/b24icons-vue/common-b24/UserIcon'
import Shield2ContourIcon from '@bitrix24/b24icons-vue/main/Shield2ContourIcon'

const route = useRoute()
const router = useRouter()

const items: TabsItem[] = [
  {
    label: 'Account',
    icon: UserIcon,
    value: 'account'
  },
  {
    label: 'Password',
    icon: Shield2ContourIcon,
    value: 'password'
  }
]

const active = computed({
  get() {
    return (route.query.tab as string) || 'account'
  },
  set(tab) {
    // Hash is specified here to prevent the page from scrolling to the top
    router.push({
      path: '/b24ui/docs/components/tabs',
      query: { tab },
      hash: '#with-route-query'
    })
  }
})
</script>

<template>
  <B24Tabs v-model="active" :content="false" :items="items" class="w-full" />
</template>

With content slot

Use the #content slot to customize the content of each item.

This is the Account tab.

<script setup lang="ts">
import type { TabsItem } from '@bitrix24/b24ui-nuxt'
import UserIcon from '@bitrix24/b24icons-vue/common-b24/UserIcon'
import Shield2ContourIcon from '@bitrix24/b24icons-vue/main/Shield2ContourIcon'

const items: TabsItem[] = [
  {
    label: 'Account',
    icon: UserIcon
  },
  {
    label: 'Password',
    icon: Shield2ContourIcon
  }
]
</script>

<template>
  <B24Tabs :items="items" class="w-full">
    <template #content="{ item }">
      <p>This is the {{ item.label }} tab.</p>
    </template>
  </B24Tabs>
</template>

With custom slot

Use the slot property to customize a specific item.

You will have access to the following slots:

  • #{{ item.slot }}

Make changes to your account here. Click save when you're done.

<script setup lang="ts">
import type { TabsItem } from '@bitrix24/b24ui-nuxt'
import UserIcon from '@bitrix24/b24icons-vue/common-b24/UserIcon'
import Shield2ContourIcon from '@bitrix24/b24icons-vue/main/Shield2ContourIcon'

const items = [
  {
    label: 'Account',
    description: 'Make changes to your account here. Click save when you\'re done.',
    icon: UserIcon,
    slot: 'account' as const
  },
  {
    label: 'Password',
    description: 'Change your password here. After saving, you\'ll be logged out.',
    icon: Shield2ContourIcon,
    slot: 'password' as const
  }
] satisfies TabsItem[]

const state = reactive({
  name: 'System User',
  username: 'system-user',
  currentPassword: '',
  newPassword: '',
  confirmPassword: ''
})
</script>

<template>
  <B24Tabs :items="items" variant="link" :b24ui="{ trigger: 'grow' }" class="gap-4 w-full">
    <template #account="{ item }">
      <ProseP accent="less" class="mb-4">
        {{ item.description }}
      </ProseP>

      <B24Form :state="state" class="flex flex-col gap-4">
        <B24FormField label="Name" name="name">
          <B24Input v-model="state.name" class="w-full" />
        </B24FormField>
        <B24FormField label="Username" name="username">
          <B24Input v-model="state.username" class="w-full" />
        </B24FormField>

        <B24Button label="Save changes" type="submit" color="air-primary-success" class="self-end" />
      </B24Form>
    </template>

    <template #password="{ item }">
      <ProseP accent="less" class="mb-4">
        {{ item.description }}
      </ProseP>

      <B24Form :state="state" class="flex flex-col gap-4">
        <B24FormField label="Current Password" name="current" required>
          <B24Input v-model="state.currentPassword" type="password" required class="w-full" />
        </B24FormField>
        <B24FormField label="New Password" name="new" required>
          <B24Input v-model="state.newPassword" type="password" required class="w-full" />
        </B24FormField>
        <B24FormField label="Confirm Password" name="confirm" required>
          <B24Input v-model="state.confirmPassword" type="password" required class="w-full" />
        </B24FormField>

        <B24Button label="Change password" type="submit" color="air-primary-success" class="self-end" />
      </B24Form>
    </template>
  </B24Tabs>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
import type { TabsItem } from '@bitrix24/b24ui-nuxt'
import UserIcon from '@bitrix24/b24icons-vue/common-b24/UserIcon'
import Shield2ContourIcon from '@bitrix24/b24icons-vue/main/Shield2ContourIcon'

const items = [
  {
    label: 'Account',
    description: 'Make changes to your account here. Click save when you\'re done.',
    icon: UserIcon,
    slot: 'account' as const
  },
  {
    label: 'Password',
    description: 'Change your password here. After saving, you\'ll be logged out.',
    icon: Shield2ContourIcon,
    slot: 'password' as const
  }
] satisfies TabsItem[]

const state = reactive({
  name: 'System User',
  username: 'system-user',
  currentPassword: '',
  newPassword: '',
  confirmPassword: ''
})
</script>

<template>
  <B24Tabs :items="items" variant="link" :b24ui="{ trigger: 'grow' }" class="gap-4 w-full">
    <template #account="{ item }">
      <ProseP accent="less" class="mb-4">
        {{ item.description }}
      </ProseP>

      <B24Form :state="state" class="flex flex-col gap-4">
        <B24FormField label="Name" name="name">
          <B24Input v-model="state.name" class="w-full" />
        </B24FormField>
        <B24FormField label="Username" name="username">
          <B24Input v-model="state.username" class="w-full" />
        </B24FormField>

        <B24Button label="Save changes" type="submit" color="air-primary-success" class="self-end" />
      </B24Form>
    </template>

    <template #password="{ item }">
      <ProseP accent="less" class="mb-4">
        {{ item.description }}
      </ProseP>

      <B24Form :state="state" class="flex flex-col gap-4">
        <B24FormField label="Current Password" name="current" required>
          <B24Input v-model="state.currentPassword" type="password" required class="w-full" />
        </B24FormField>
        <B24FormField label="New Password" name="new" required>
          <B24Input v-model="state.newPassword" type="password" required class="w-full" />
        </B24FormField>
        <B24FormField label="Confirm Password" name="confirm" required>
          <B24Input v-model="state.confirmPassword" type="password" required class="w-full" />
        </B24FormField>

        <B24Button label="Change password" type="submit" color="air-primary-success" class="self-end" />
      </B24Form>
    </template>
  </B24Tabs>
</template>

API

Props

Prop Default Type
as'div'any

The element or component this component should render as.

items T[]
variant'link' "link"
size'md' "sm" | "xs" | "md" | "xss" | "lg" | "xl"
orientation'horizontal' "horizontal" | "vertical"

The orientation of the tabs.

contenttrueboolean

The content of the tabs, can be disabled to prevent rendering the content.

valueKey'value' keyof Extract<NestedItem<T>, object> & string | DotPathKeys<Extract<NestedItem<T>, object>>

The key used to get the value from the item.

labelKey'label' keyof Extract<NestedItem<T>, object> & string | DotPathKeys<Extract<NestedItem<T>, object>>

The key used to get the label from the item.

defaultValue'0' string | number

The value of the tab that should be active when initially rendered. Use when you do not need to control the state of the tabs

modelValue string | number

The controlled value of the tab to activate. Can be bind as v-model.

activationModeautomatic "automatic" | "manual"

Whether a tab is activated automatically (on focus) or manually (on click).

unmountOnHidetrueboolean

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

b24ui { root?: ClassNameValue; list?: ClassNameValue; indicator?: ClassNameValue; trigger?: ClassNameValue; leadingIcon?: ClassNameValue; leadingAvatar?: ClassNameValue; leadingAvatarSize?: ClassNameValue; label?: ClassNameValue; trailingBadge?: ClassNameValue; trailingBadgeSize?: ClassNameValue; content?: ClassNameValue; }

Slots

Slot Type
leading{ item: T; index: number; b24ui: object; }
default{ item: T; index: number; }
trailing{ item: T; index: number; b24ui: object; }
content{ item: T; index: number; b24ui: object; }
list-leading{}
list-trailing{}

Emits

Event Type
update:modelValue[payload: string | number]

Expose

When accessing the component via a template ref, you can use the following:

NameType
triggersRefRef<ComponentPublicInstance[]>

Theme

https://github.com/bitrix24/b24ui/tree/main/src/theme/tabs.ts
export default {
  slots: {
    root: 'style-outline-accent-2 flex items-center gap-2',
    list: 'relative flex p-1 group',
    indicator: 'absolute transition-[translate,width] duration-200',
    trigger: 'group relative inline-flex items-center min-w-0 data-[state=inactive]:text-(--ui-color-design-plain-na-content) hover:data-[state=inactive]:not-disabled:text-(--ui-color-design-selection-content) font-(--ui-font-weight-medium) cursor-pointer disabled:cursor-not-allowed disabled:opacity-30 transition-colors rounded-(--ui-border-radius-md)',
    leadingIcon: 'shrink-0',
    leadingAvatar: 'shrink-0',
    leadingAvatarSize: '',
    label: '',
    trailingBadge: 'shrink-0',
    trailingBadgeSize: 'sm',
    content: 'focus:outline-none w-full'
  },
  variants: {
    variant: {
      link: {
        list: 'border-(--ui-color-divider-vibrant-accent-more)',
        indicator: 'rounded-(--ui-border-radius-pill)',
        trigger: 'focus:outline-none'
      }
    },
    orientation: {
      horizontal: {
        root: 'flex-col',
        list: 'w-full',
        indicator: 'left-0 w-(--reka-tabs-indicator-size) translate-x-(--reka-tabs-indicator-position)',
        trigger: 'justify-center'
      },
      vertical: {
        list: 'flex-col',
        indicator: 'top-0 h-(--reka-tabs-indicator-size) translate-y-(--reka-tabs-indicator-position)'
      }
    },
    size: {
      xss: {
        trigger: 'px-2 py-1 text-(length:--ui-font-size-4xs)/[normal] gap-1',
        leadingIcon: 'size-4',
        leadingAvatarSize: '3xs'
      },
      xs: {
        trigger: 'px-2 py-1 text-(length:--ui-font-size-xs)/[normal] gap-1',
        leadingIcon: 'size-4',
        leadingAvatarSize: '3xs'
      },
      sm: {
        trigger: 'px-2.5 py-1.5 text-(length:--ui-font-size-sm)/[normal] gap-1.5',
        leadingIcon: 'size-4',
        leadingAvatarSize: '3xs'
      },
      md: {
        trigger: 'px-3 py-1.5 text-(length:--ui-font-size-md)/[normal] gap-1.5',
        leadingIcon: 'size-5',
        leadingAvatarSize: '2xs'
      },
      lg: {
        trigger: 'px-3 py-2 text-(length:--ui-font-size-lg)/[normal] gap-2',
        leadingIcon: 'size-5',
        leadingAvatarSize: '2xs'
      },
      xl: {
        trigger: 'px-3 py-2 text-(length:--ui-font-size-xl)/[normal] gap-2',
        leadingIcon: 'size-6',
        leadingAvatarSize: 'xs'
      }
    }
  },
  compoundVariants: [
    {
      orientation: 'horizontal',
      variant: 'link',
      class: {
        list: 'border-b -mb-px',
        indicator: '-bottom-px h-px'
      }
    },
    {
      orientation: 'vertical',
      variant: 'link',
      class: {
        list: 'border-s -ms-px',
        indicator: '-start-px w-px'
      }
    },
    {
      variant: 'link',
      class: {
        indicator: 'bg-(--ui-color-design-selection-content)',
        trigger: [
          'focus-visible:ring-1 focus-visible:ring-inset',
          'data-[state=active]:text-(--b24ui-color)',
          'focus-visible:ring-(--ui-color-design-selection-content)'
        ]
      }
    }
  ],
  defaultVariants: {
    variant: 'link',
    size: 'md'
  }
}