Tabs
UiTabs renders a keyboard-accessible tab list (arrow keys, Home/End). Pass an array of tab items and bind the selection with v-model:active-tab.
Add tabs
<script setup lang="ts">
import type { ITabItem } from '@colorffy/ui'
import { ref } from 'vue'
const tabs: ITabItem[] = [
{ id: 'overview', label: 'Overview' },
{ id: 'pricing', label: 'Pricing' },
{ id: 'faq', label: 'FAQ', disabled: true },
]
const active = ref('overview')
</script>
<template>
<UiTabs :tabs="tabs" v-model:active-tab="active" />
<section v-show="active === 'overview'">Overview panel</section>
<section v-show="active === 'pricing'">Pricing panel</section>
</template>
Add a badge to a tab
Each tab item accepts an optional badge (a Partial<IBadgeProps>) rendered after the label — handy for counts or status hints.
<script setup lang="ts">
const tabs: ITabItem[] = [
{ id: 'inbox', label: 'Inbox', badge: { text: '12', variant: 'primary', pill: true } },
{ id: 'archived', label: 'Archived', badge: { text: 'New', variant: 'success' } },
{ id: 'spam', label: 'Spam' },
]
</script>
Style as pills or contrast
<template>
<UiTabs :tabs="tabs" pill-tabs />
<UiTabs :tabs="tabs" contrast-tabs />
</template>
Add a leading icon to a tab
Each tab item accepts an optional icon (a Material Symbols entity code, same convention as INavItem.icon) rendered before the label.
<script setup lang="ts">
const tabs: ITabItem[] = [
{ id: 'overview', label: 'Overview', icon: '' },
{ id: 'details', label: 'Details', icon: '' },
]
</script>
Icon-only tabs
Set iconOnly to show only the icon of tabs that have one, as square buttons. The label stays the tab's accessible name (visually hidden) and shows as a native tooltip on hover. Tabs without an icon keep their label, and badges still render.
<template>
<UiTabs :tabs="tabs" pill-tabs rounded icon-only />
</template>
Fluid (equal-width) tabs
Set fluid to stretch every tab to fill the available width equally.
<template>
<UiTabs :tabs="tabs" fluid />
</template>
Small tabs
Set size="sm" to render the tab buttons at the default button height and font size instead of the larger tab defaults.
<template>
<UiTabs :tabs="tabs" pill-tabs size="sm" />
</template>
Fit and rounded pill tabs
By default the tab bar spans its container. Set fit to make it only as wide as its tabs, and rounded to make pill tabs and their indicator fully rounded. rounded only applies together with pillTabs, and fit is ignored when fluid is set.
<template>
<UiTabs :tabs="tabs" pill-tabs fit rounded />
</template>
Overflow scrolling
When the tab list is wider than its container, it scrolls horizontally with a thin scrollbar instead of wrapping or clipping. No extra prop is needed — this is the default behavior of UiTabs.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
tabs | ITabItem[] | — | Tab items: { id, label, disabled?, panelId?, badge?, icon? }. |
activeTab | string | first tab | Id of the active tab. |
pillTabs | boolean | false | Pill styling. |
contrastTabs | boolean | false | Contrast styling. |
fluid | boolean | false | Equal-width tabs that stretch to fill the available space. |
fit | boolean | false | The tab bar is only as wide as its tabs. Ignored when fluid is set. |
rounded | boolean | false | Fully rounded pill tabs and indicator. Requires pillTabs. |
iconOnly | boolean | false | Tabs with an icon show only the icon; the label becomes the accessible name and a hover tooltip. |
size | 'sm' | 'md' | null | null | 'sm' uses the default button height and font size. 'md'/null keep the larger tab size. |
Emits: update:activeTab(tabId: string), used by v-model:active-tab.
pillTabs) is positioned and animated entirely in CSS with anchor positioning — no JavaScript is involved, and the slide is skipped under prefers-reduced-motion: reduce. Where anchor positioning is unsupported, the active tab falls back to its own bottom border — or to a filled, shadowed background for pillTabs — so nothing is lost. The slide covers up to 16 tabs; past that the indicator still lands correctly, it just jumps.UiSegmentedControls; it takes the same tab items.CSS variables
Set these on :root, or on any wrapper to restyle only the tabs inside it. The --cffy-tabs-pills-* variables only affect pillTabs; contrastTabs and rounded keep their own background and radius. Hover and pressed backgrounds come from the shared state layer.
:root {
--cffy-tabs-indicator-color: var(--cffy-accent-a10);
--cffy-tabs-link-active-color: var(--cffy-primary-a10);
--cffy-tabs-pills-bg-color: var(--cffy-surface-container);
}
| Variable | Default | Controls |
|---|---|---|
--cffy-tabs-gap | --cffy-space-8 | Space between tabs |
--cffy-tabs-border-color | --cffy-outline-button | Bottom border of underline tabs |
--cffy-tabs-indicator-color / --cffy-tabs-indicator-size | --cffy-primary-a10 / 3px | Underline indicator |
--cffy-tabs-easing / --cffy-tabs-duration | --cffy-ease-decelerate / --cffy-duration-300 | Indicator slide |
--cffy-tabs-link-color / --cffy-tabs-link-font-weight | --cffy-muted-a10 / --cffy-fw-500 | Tab text |
--cffy-tabs-link-hover-color | --cffy-on-background | Hovered tab text |
--cffy-tabs-link-active-color | --cffy-on-background | Active tab text |
--cffy-tabs-pills-bg-color | --cffy-surface-container-low | Pill tabs container |
--cffy-tabs-pills-radius | 1.5 × --cffy-shape-control (12px) | Pill and indicator corners (the container follows) |
--cffy-tabs-pills-indicator-bg-color / --cffy-tabs-pills-indicator-shadow | --cffy-surface-body-pane / --cffy-shadow-md | Raised pill behind the active tab |