Skip to content
bref / ui

Svelte components, simply.

Star

Tabs

Switch between sections with primary-colored floating pills and soft glass selection, horizontally or vertically.

Horizontal

Workspace

Overview

The selected section is rendered here by the consumer.

Horizontal usage code
Svelte
<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

Workspace

Overview

Filled icons, with content beside the strip.

Vertical usage code
Svelte
<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

Svelte
<Tabs {items} bind:value size="small" label="Workspace sections" />

Playground

Live preview

Selected: overview; changes: 0

Settings

Appearance
Selection
View generated code
Svelte
<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

Component props
PropTypeRequiredDefaultDescription
itemsreadonly TabItemDataProps[]Yes—Display order; each item has a unique id, label, icon and optional disabled flag.
orientationOrientationNohorizontalSoft glass pills in a horizontal row or equal-width vertical column; determines arrow-key navigation.
widthDimensionNofitHorizontal only: fit keeps natural item widths and scrolls; fill spans the parent and truncates labels. Vertical rejects width.
sizeBaseSizeNomediumScales tab height, padding, label and icon in both orientations. Coarse pointers keep 44px touch targets.
valuestringNoundefinedBindable 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) => voidNoOmittedCalled once when user interaction changes selection.
labelstringNoTabsAccessible name for the tab list.
filledbooleanNofalseFill every Material Symbols icon; the selected icon is always filled. Does not change the soft glass background.
transitionInTabTransitionNoShared crossfadeCustom intro for the selected indicator; receives its HTMLElement.
transitionOutTabTransitionNoShared crossfadeCustom 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.