Organize related content into tabbed views with TabList, Tab, TabPanel, and TabItem.
Introduction
Tabs organize related content into tabbed views. Compose with TabList, Tab, and TabPanel, or use the TabItem shortcut. The selected accent uses the full semantic color palette; inactive tabs use dark-*.
Use the framework selector in the site header to switch between React and Vue.
Import
import { Tabs } from "@bridge-ui/vue/Components/Tabs";
import { Tab } from "@bridge-ui/vue/Components/Tab";
import { TabItem } from "@bridge-ui/vue/Components/TabItem";
import { TabList } from "@bridge-ui/vue/Components/TabList";
import { TabPanel } from "@bridge-ui/vue/Components/TabPanel";import { Tabs } from "@bridge-ui/react/Components/Tabs";
import { Tab } from "@bridge-ui/react/Components/Tab";
import { TabItem } from "@bridge-ui/react/Components/TabItem";
import { TabList } from "@bridge-ui/react/Components/TabList";
import { TabPanel } from "@bridge-ui/react/Components/TabPanel";Basic usage
Control selection with value / onChange (React) or v-model (Vue). Each Tab and TabPanel shares a value.
<script setup lang="ts">
import { ref } from "vue";
import { Tab } from "@bridge-ui/vue/Components/Tab";
import { TabList } from "@bridge-ui/vue/Components/TabList";
import { TabPanel } from "@bridge-ui/vue/Components/TabPanel";
import { Tabs } from "@bridge-ui/vue/Components/Tabs";
const pkg = ref("bun");
</script>
<template>
<Tabs v-model="pkg">
<TabList aria-label="Package manager">
<Tab value="bun">bun</Tab>
<Tab value="npm">npm</Tab>
<Tab value="pnpm">pnpm</Tab>
</TabList>
<TabPanel value="bun">bun install</TabPanel>
<TabPanel value="npm">npm install</TabPanel>
<TabPanel value="pnpm">pnpm install</TabPanel>
</Tabs>
</template>
import { useState } from "react";
import { Tab } from "@bridge-ui/react/Components/Tab";
import { TabList } from "@bridge-ui/react/Components/TabList";
import { TabPanel } from "@bridge-ui/react/Components/TabPanel";
import { Tabs } from "@bridge-ui/react/Components/Tabs";
export default function TabsBasic() {
const [pkg, setPkg] = useState("bun");
return (
<Tabs value={pkg} onChange={setPkg}>
<TabList aria-label="Package manager">
<Tab value="bun">bun</Tab>
<Tab value="npm">npm</Tab>
<Tab value="pnpm">pnpm</Tab>
</TabList>
<TabPanel value="bun">bun install</TabPanel>
<TabPanel value="npm">npm install</TabPanel>
<TabPanel value="pnpm">pnpm install</TabPanel>
</Tabs>
);
}
TabItem shortcut
TabItem renders both the tab trigger and its panel. Pass label for the trigger and children for the panel content.
<script setup lang="ts">
import { ref } from "vue";
import { TabItem } from "@bridge-ui/vue/Components/TabItem";
import { Tabs } from "@bridge-ui/vue/Components/Tabs";
const pkg = ref("bun");
</script>
<template>
<Tabs v-model="pkg">
<TabItem label="bun" value="bun">bun install</TabItem>
<TabItem label="npm" value="npm">npm install</TabItem>
<TabItem label="pnpm" value="pnpm">pnpm install</TabItem>
</Tabs>
</template>
import { TabItem } from "@bridge-ui/react/Components/TabItem";
import { Tabs } from "@bridge-ui/react/Components/Tabs";
export default function TabsTabItem() {
return (
<Tabs defaultValue="bun">
<TabItem label="bun" value="bun">
bun install
</TabItem>
<TabItem label="npm" value="npm">
npm install
</TabItem>
<TabItem label="pnpm" value="pnpm">
pnpm install
</TabItem>
</Tabs>
);
}
Icons
Pass icons with startIcon / endIcon, or custom nodes via slots.start / slots.end.
<script setup lang="ts">
import { Building2, User, Users } from "@lucide/vue";
import { ref } from "vue";
import { Tab } from "@bridge-ui/vue/Components/Tab";
import { TabList } from "@bridge-ui/vue/Components/TabList";
import { TabPanel } from "@bridge-ui/vue/Components/TabPanel";
import { Tabs } from "@bridge-ui/vue/Components/Tabs";
const section = ref("account");
</script>
<template>
<Tabs v-model="section">
<TabList aria-label="Settings">
<Tab value="account" :start-icon="User">My Account</Tab>
<Tab value="company" :start-icon="Building2">Company</Tab>
<Tab value="team" :start-icon="Users">Team Members</Tab>
</TabList>
<TabPanel value="account">Account settings</TabPanel>
<TabPanel value="company">Company profile</TabPanel>
<TabPanel value="team">Team members</TabPanel>
</Tabs>
</template>
import { Building2, User, Users } from "lucide-react";
import { Tab } from "@bridge-ui/react/Components/Tab";
import { TabList } from "@bridge-ui/react/Components/TabList";
import { TabPanel } from "@bridge-ui/react/Components/TabPanel";
import { Tabs } from "@bridge-ui/react/Components/Tabs";
export default function TabsIcons() {
return (
<Tabs defaultValue="account">
<TabList aria-label="Settings">
<Tab value="account" startIcon={User}>
My Account
</Tab>
<Tab value="company" startIcon={Building2}>
Company
</Tab>
<Tab value="team" startIcon={Users}>
Team Members
</Tab>
</TabList>
<TabPanel value="account">Account settings</TabPanel>
<TabPanel value="company">Company profile</TabPanel>
<TabPanel value="team">Team members</TabPanel>
</Tabs>
);
}
Colors
Use the color prop for the selected tab accent. Available colors: primary, secondary, success, info, warning, error, and dark.
<script setup lang="ts">
import { Tab } from "@bridge-ui/vue/Components/Tab";
import { TabList } from "@bridge-ui/vue/Components/TabList";
import { TabPanel } from "@bridge-ui/vue/Components/TabPanel";
import { Tabs } from "@bridge-ui/vue/Components/Tabs";
const colors = [
"dark",
"info",
"error",
"primary",
"success",
"warning",
"secondary",
] as const;
</script>
<template>
<div class="flex w-full flex-col gap-6">
<Tabs :key="color" :color="color" v-for="color in colors">
<TabList :aria-label="`${color} tabs`">
<Tab value="one">{{ color }}</Tab>
<Tab value="two">Two</Tab>
<Tab value="three">Three</Tab>
</TabList>
<TabPanel value="one">First panel ({{ color }})</TabPanel>
<TabPanel value="two">Second panel</TabPanel>
<TabPanel value="three">Third panel</TabPanel>
</Tabs>
</div>
</template>
import { Tab } from "@bridge-ui/react/Components/Tab";
import { TabList } from "@bridge-ui/react/Components/TabList";
import { TabPanel } from "@bridge-ui/react/Components/TabPanel";
import { Tabs } from "@bridge-ui/react/Components/Tabs";
const colors = [
"dark",
"info",
"error",
"primary",
"success",
"warning",
"secondary",
] as const;
export default function TabsColors() {
return (
<div className="flex w-full flex-col gap-6">
{colors.map((color) => (
<Tabs key={color} color={color} defaultValue="one">
<TabList aria-label={`${color} tabs`}>
<Tab value="one">{color}</Tab>
<Tab value="two">Two</Tab>
<Tab value="three">Three</Tab>
</TabList>
<TabPanel value="one">First panel ({color})</TabPanel>
<TabPanel value="two">Second panel</TabPanel>
<TabPanel value="three">Third panel</TabPanel>
</Tabs>
))}
</div>
);
}
Variants
Available variants: line (default), plain, pill, solid, and enclosed. Pair with any color from the palette above.
<script setup lang="ts">
import { Tab } from "@bridge-ui/vue/Components/Tab";
import { TabList } from "@bridge-ui/vue/Components/TabList";
import { TabPanel } from "@bridge-ui/vue/Components/TabPanel";
import { Tabs } from "@bridge-ui/vue/Components/Tabs";
const variants = ["line", "pill", "plain", "solid", "enclosed"] as const;
</script>
<template>
<div class="flex w-full flex-col gap-6">
<Tabs :key="variant" :variant="variant" v-for="variant in variants">
<TabList :aria-label="`${variant} tabs`">
<Tab value="one">{{ variant }}</Tab>
<Tab value="two">Two</Tab>
<Tab value="three">Three</Tab>
</TabList>
<TabPanel value="one">First panel ({{ variant }})</TabPanel>
<TabPanel value="two">Second panel</TabPanel>
<TabPanel value="three">Third panel</TabPanel>
</Tabs>
</div>
</template>
import { Tab } from "@bridge-ui/react/Components/Tab";
import { TabList } from "@bridge-ui/react/Components/TabList";
import { TabPanel } from "@bridge-ui/react/Components/TabPanel";
import { Tabs } from "@bridge-ui/react/Components/Tabs";
const variants = ["line", "pill", "plain", "solid", "enclosed"] as const;
export default function TabsVariants() {
return (
<div className="flex w-full flex-col gap-6">
{variants.map((variant) => (
<Tabs key={variant} variant={variant} defaultValue="one">
<TabList aria-label={`${variant} tabs`}>
<Tab value="one">{variant}</Tab>
<Tab value="two">Two</Tab>
<Tab value="three">Three</Tab>
</TabList>
<TabPanel value="one">First panel ({variant})</TabPanel>
<TabPanel value="two">Second panel</TabPanel>
<TabPanel value="three">Third panel</TabPanel>
</Tabs>
))}
</div>
);
}
Vertical
Set orientation="vertical" for a side-aligned tab list.
<script setup lang="ts">
import { ref } from "vue";
import { Tab } from "@bridge-ui/vue/Components/Tab";
import { TabList } from "@bridge-ui/vue/Components/TabList";
import { TabPanel } from "@bridge-ui/vue/Components/TabPanel";
import { Tabs } from "@bridge-ui/vue/Components/Tabs";
const section = ref("account");
</script>
<template>
<Tabs v-model="section" orientation="vertical">
<TabList aria-label="Account">
<Tab value="account">Account</Tab>
<Tab value="settings">Settings</Tab>
<Tab value="billing">Billing</Tab>
</TabList>
<TabPanel value="account">Account content</TabPanel>
<TabPanel value="settings">Settings content</TabPanel>
<TabPanel value="billing">Billing content</TabPanel>
</Tabs>
</template>
import { Tab } from "@bridge-ui/react/Components/Tab";
import { TabList } from "@bridge-ui/react/Components/TabList";
import { TabPanel } from "@bridge-ui/react/Components/TabPanel";
import { Tabs } from "@bridge-ui/react/Components/Tabs";
export default function TabsVertical() {
return (
<Tabs defaultValue="account" orientation="vertical">
<TabList aria-label="Account">
<Tab value="account">Account</Tab>
<Tab value="settings">Settings</Tab>
<Tab value="billing">Billing</Tab>
</TabList>
<TabPanel value="account">Account content</TabPanel>
<TabPanel value="settings">Settings content</TabPanel>
<TabPanel value="billing">Billing content</TabPanel>
</Tabs>
);
}
Manual activation
With activation="manual", arrow keys move focus without changing the selected tab until Enter or Space.
<script setup lang="ts">
import { ref } from "vue";
import { Tab } from "@bridge-ui/vue/Components/Tab";
import { TabList } from "@bridge-ui/vue/Components/TabList";
import { TabPanel } from "@bridge-ui/vue/Components/TabPanel";
import { Tabs } from "@bridge-ui/vue/Components/Tabs";
const tab = ref("a");
</script>
<template>
<Tabs v-model="tab" activation="manual">
<TabList aria-label="Manual activation">
<Tab value="a">A</Tab>
<Tab value="b">B</Tab>
<Tab value="c">C</Tab>
</TabList>
<TabPanel value="a">Panel A — press Enter or Space to activate</TabPanel>
<TabPanel value="b">Panel B</TabPanel>
<TabPanel value="c">Panel C</TabPanel>
</Tabs>
</template>
import { Tab } from "@bridge-ui/react/Components/Tab";
import { TabList } from "@bridge-ui/react/Components/TabList";
import { TabPanel } from "@bridge-ui/react/Components/TabPanel";
import { Tabs } from "@bridge-ui/react/Components/Tabs";
export default function TabsManual() {
return (
<Tabs defaultValue="a" activation="manual">
<TabList aria-label="Manual activation">
<Tab value="a">A</Tab>
<Tab value="b">B</Tab>
<Tab value="c">C</Tab>
</TabList>
<TabPanel value="a">Panel A — press Enter or Space to activate</TabPanel>
<TabPanel value="b">Panel B</TabPanel>
<TabPanel value="c">Panel C</TabPanel>
</Tabs>
);
}
Nested customProps
With TabItem, Tabs maps list/triggers/panels and merges customProps.tabList, customProps.tab, and customProps.tabPanel. Item-owned props (value, icons, label, keepMounted, panel content) are not overridable via nested customProps. TabItem slots are not shallow-merged from customProps.tab.
<Tabs
defaultValue="bun"
customProps={{
tabList: { className: "gap-2" },
tab: { classes: { root: "px-3" } },
}}
>
<TabItem label="bun" value="bun">
bun install
</TabItem>
<TabItem label="npm" value="npm">
npm install
</TabItem>
</Tabs>Related components
API
Tabs
| Prop | Type | Default |
|---|---|---|
| v-model / modelValue | string | first registered tab |
| @update:model-value | (value: string) => void | — |
| Prop | Type | Default |
|---|---|---|
| value | string | — |
| defaultValue | string | first registered tab |
| onChange | (value: string) => void | — |
| Prop | Type | Default | Description |
|---|---|---|---|
| orientation | TabsOrientation | "horizontal" | Layout orientation of the tab list. |
| variant | TabsVariant | "line" | Visual style of the tab list / selected tab. |
| color | TabsColor | "primary" | Accent color for the selected tab. |
| size | TabsSize | "md" | Size of the tab list and triggers. |
| activation | TabsActivation | "automatic" | Keyboard activation: automatic selects on arrow focus; manual needs Enter/Space. |
| keepMounted | boolean | true | When true, inactive panels stay mounted (hidden). |
| customProps | TabsCustomProps | — | With TabItem, forwards props to mapped TabList / Tab / TabPanel. Item-owned props stay on each item. |
Tab
| Prop | Type | Default |
|---|---|---|
| value | string | required |
| startIcon | IconSource | — |
| endIcon | IconSource | — |
| disabled | boolean | false |
| slots | { start?, end? } | — |
TabList
HTML attributes on the role="tablist" container (aria-label, className, etc.).
TabPanel
| Prop | Type | Default |
|---|---|---|
| value | string | required |
| keepMounted | boolean | inherits from Tabs |
TabItem
| Prop | Type | Default |
|---|---|---|
| value | string | required |
| label | ReactNode / Vue slot content | required |
| startIcon | IconSource | — |
| endIcon | IconSource | — |
| disabled | boolean | false |
| keepMounted | boolean | inherits from Tabs |
| slots | { start?, end? } | — |
| children | panel content | — |