Tabs
Switch between sections with primary-colored floating pills and soft glass selection, horizontally or vertically.
Horizontal
Overview
The selected section is rendered here by the consumer.
Horizontal usage code
<script lang="ts">
import { Tabs } from 'bref-ui';
let value = $state('overview');
const items = [
{
id: 'overview',
label: 'Overview',
icon: 'dashboard'
},
{
id: 'activity',
label: 'Activity',
icon: 'history'
},
{
id: 'archived',
label: 'Archived',
icon: 'archive',
disabled: true
},
{
id: 'settings',
label: 'Settings',
icon: 'settings'
}
] as const;
</script>
<Tabs {items} bind:value orientation="horizontal" label="Workspace sections" />
{#if value === 'overview'}
<p>Workspace overview</p>
{:else}
<p>Workspace settings</p>
{/if}
Vertical
Overview
Filled icons, with content beside the strip.
Vertical usage code
<script lang="ts">
import { Tabs } from 'bref-ui';
let value = $state('overview');
const items = [
{
id: 'overview',
label: 'Overview',
icon: 'dashboard'
},
{
id: 'activity',
label: 'Activity',
icon: 'history'
},
{
id: 'archived',
label: 'Archived',
icon: 'archive',
disabled: true
},
{
id: 'settings',
label: 'Settings',
icon: 'settings'
}
] as const;
</script>
<Tabs {items} bind:value orientation="vertical" label="Workspace sections" filled />
{#if value === 'overview'}
<p>Workspace overview</p>
{:else}
<p>Workspace settings</p>
{/if}
Sizes
small
medium
large
<Tabs {items} bind:value size="small" label="Workspace sections" />
Playground
Live preview
Settings
View generated code
<script lang="ts">
import { Tabs } from 'bref-ui';
const items = [
{
id: 'overview',
label: 'Overview',
icon: 'dashboard'
},
{
id: 'activity',
label: 'Activity',
icon: 'history'
},
{
id: 'archived',
label: 'Archived',
icon: 'archive',
disabled: true
},
{
id: 'settings',
label: 'Settings',
icon: 'settings'
}
] as const;
let value = $state<string | undefined>('overview');
let changes = $state(0);
</script>
<Tabs
{items}
bind:value
orientation="horizontal"
width="fit"
size="medium"
label={'Preview sections'}
filled={false}
onChange={() => changes++}
/>
<p role="status">Selected: {value ?? 'none'}; changes: {changes}</p>
States and overflow
No initial selection
Unavailable selection
Selected ID: overview
Remove or disable Overview, then press Tab from Select Overview. The first enabled tab remains reachable; the selected ID changes only when a tab is activated.
All tabs disabled
Long labels in a narrow container
Fit: natural widths with scrolling.
Fill: parent width with truncated labels.
Vertical: truncated labels with vertical scrolling.
Horizontal tabs support fit and fill: fit keeps natural label widths and scrolls; fill spans the parent and truncates with ellipses. Vertical tabs always truncate overflowing labels and scroll vertically when height is constrained. At 575px and below, horizontal tabs show only the selected label and vertical tabs hide every label. Every tab requires an icon and keeps its full accessible name.
Arrow keys follow the orientation, wrap and skip disabled tabs. Home and End select the first and last enabled tabs. Enter or Space selects the focused tab; Tab leaves the strip.
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
items | readonly TabItemDataProps[] | Yes | — | Display order; each item has a unique id, label, icon and optional disabled flag. |
orientation | Orientation | No | horizontal | Soft glass pills in a horizontal row or equal-width vertical column; determines arrow-key navigation. |
width | Dimension | No | fit | Horizontal only: fit keeps natural item widths and scrolls; fill spans the parent and truncates labels. Vertical rejects width. |
size | BaseSize | No | medium | Scales tab height, padding, label and icon in both orientations. Coarse pointers keep 44px touch targets. |
value | string | No | undefined | Bindable selected ID. If missing or disabled, the first enabled tab stays focusable without changing the value. Omitted values preserve native Tab order. |
onChange | (id: string) => void | No | Omitted | Called once when user interaction changes selection. |
label | string | No | Tabs | Accessible name for the tab list. |
filled | boolean | No | false | Fill every Material Symbols icon; the selected icon is always filled. Does not change the soft glass background. |
transitionIn | TabTransition | No | Shared crossfade | Custom intro for the selected indicator; receives its HTMLElement. |
transitionOut | TabTransition | No | Shared crossfade | Custom outro for the selected indicator; receives its HTMLElement. |
Only the tab strip is rendered. Content and navigation belong to the consumer; no snippets or native HTML attributes/events are forwarded. Use bind:value or onChange for selection. Icons are decorative; each item's label names its tab.
Without a selected ID, enabled tabs keep native Tab order. A matching enabled selected ID gives its tab the only Tab stop. If that ID is missing or disabled, the first enabled tab becomes the Tab stop without changing selection or calling onChange. Empty and fully disabled strips have no Tab stop. The shared theme control switches light and dark modes.
Custom transitions return a Svelte TransitionConfig or a function returning one. Use closures to supply parameters; omitted transitions use the shared crossfade.