Tabs
Import
Section titled “Import”Tabs is a single component. The tab buttons are declared as data via the tabs
prop, and the active tab is tracked by its index with bind:tab.
import { Tabs } from '@delightstack/components';import type { TabItem } from '@delightstack/components';Basic Usage
Section titled “Basic Usage”Each entry in tabs describes one tab button. Give it a content snippet to render
that tab’s panel — label and content stay together in one place, and only the active
panel is rendered.
Welcome to the overview panel. Get a quick summary of everything you need to know.
View code
<script> import { Tabs } from '@delightstack/components';
let tab = $state(0);</script>
{#snippet overview()} <p>Welcome to the overview panel.</p>{/snippet}{#snippet features()} <p>Explore the features.</p>{/snippet}{#snippet pricing()} <p>View our pricing plans.</p>{/snippet}
<Tabs bind:tab tabs={[ { label: 'Overview', content: overview }, { label: 'Features', content: features }, { label: 'Pricing', content: pricing },]} />Inline content
Section titled “Inline content”If you’d rather not write a content snippet per tab, omit it and pass children
instead. The children render in the panel area and receive the active tab index,
so you can gate them however you like:
<Tabs bind:tab tabs={[{ label: 'Inbox' }, { label: 'Sent' }]}> {#if tab === 0} <p>Your inbox.</p> {:else if tab === 1} <p>Your sent mail.</p> {/if}</Tabs>The children snippet also receives { tab, select }, so
{#snippet children({ tab, select })} works too if you want the current index or a
programmatic select(i) helper.
Examples
Section titled “Examples”Pill Variant
Section titled “Pill Variant”Rounded pill-shaped tab buttons. The active pill fills with the action color.
Everything in one place.
View code
<Tabs bind:tab pills tabs={[ { label: 'All' }, { label: 'Active', badge: '3' }, { label: 'Archived' },]} />Boxed Variant
Section titled “Boxed Variant”An enclosed, segmented-control style group where the active tab rides on an elevated surface that glides between options.
The details panel.
View code
<Tabs bind:tab boxed tabs={[ { label: 'Details' }, { label: 'Activity' }, { label: 'Settings' },]} />Content Transitions
Section titled “Content Transitions”Set transition to animate the panel as the active tab changes. slide moves the
content in the direction you navigated; fade cross-dissolves; none (the default)
swaps instantly. All transitions respect prefers-reduced-motion.
The smallest planet, closest to the Sun.
View code
<Tabs bind:tab transition="slide" tabs={[ { label: 'Mercury', content: mercury }, { label: 'Venus', content: venus }, { label: 'Earth', content: earth },]} />Vertical Orientation
Section titled “Vertical Orientation”Stack tabs for sidebar-style navigation. The list and the active panel sit side by side automatically — no wrapper layout needed.
General settings — your name, language, and time zone.
View code
<Tabs bind:tab orientation="vertical" tabs={[ { label: 'General', content: general }, { label: 'Security', content: security }, { label: 'Notifications', content: notifications },]} />With Badges
Section titled “With Badges”Give a tab a badge (a count or short string). Inactive badges are tinted; the
active one inverts so it stays legible.
12 unread messages.
View code
<Tabs bind:tab tabs={[ { label: 'Inbox', badge: 12 }, { label: 'Sent' }, { label: 'Drafts', badge: '2' },]} />Full Width
Section titled “Full Width”Stretch tab buttons to fill the available width evenly.
The first panel.
View code
<Tabs bind:tab full_width tabs={[ { label: 'First' }, { label: 'Second' }, { label: 'Third' },]} />Disabled
Section titled “Disabled”Disable an individual tab with disabled: true on its entry, or disable the whole
group with the container’s disabled prop. Disabled tabs are skipped during keyboard
navigation.
This feature is ready to use.
View code
<Tabs bind:tab tabs={[ { label: 'Available' }, { label: 'Coming Soon', disabled: true }, { label: 'Beta' },]} />Skeleton Loading
Section titled “Skeleton Loading”Show shimmering placeholders while the real tabs load. The placeholders match the final list’s height so nothing shifts when content arrives.
View code
<Tabs skeleton={loading} skeleton_count={3} bind:tab tabs={tabs} />| Prop | Type | Default | Description |
|---|---|---|---|
tab | number | 0 | Index of the active tab ($bindable) |
tabs | TabItem[] | [] | The tab buttons to render, in order. The array index is the tab’s value. |
transition | 'none' | 'fade' | 'slide' | 'none' | How the panel animates between tabs |
pills | boolean | false | Use pill-shaped tab buttons |
boxed | boolean | false | Use boxed / segmented-control tab style |
orientation | 'horizontal' | 'vertical' | 'horizontal' | Tab list orientation |
size | '0' | '1' | '2' | '3' | '1' | Size of the tabs |
full_width | boolean | false | Stretch tabs to fill available width |
disabled | boolean | false | Disable all tabs |
skeleton | boolean | false | Show skeleton loading state |
skeleton_count | number | 3 | Number of skeleton tab placeholders |
onchange | (detail: { tab: number }) => void | undefined | Called when the active tab changes |
id | string | auto | Element ID |
class | string | '' | Additional CSS classes |
children | Snippet<[{ tab, select }]> | undefined | Panel content used when a tab has no content snippet |
TabItem
Section titled “TabItem”Each entry in the tabs array:
| Field | Type | Default | Description |
|---|---|---|---|
label | string | — | The tab button’s label text |
badge | string | number | undefined | A badge shown after the label |
disabled | boolean | false | Disable this individual tab |
content | Snippet | undefined | This tab’s panel content |
Events
Section titled “Events”| Event | Detail | Description |
|---|---|---|
onchange | { tab: number } | Fires when the active tab index changes |
interface TabItem { label: string; badge?: string | number; disabled?: boolean; content?: Snippet;}
type TabsTransition = 'none' | 'fade' | 'slide';Accessibility
Section titled “Accessibility”- The tab list uses
role="tablist"witharia-orientation - Each button uses
role="tab"witharia-selectedandaria-controls - The panel uses
role="tabpanel"witharia-labelledbypointing at the active tab - Arrow Left / Right (or Up / Down when vertical) moves between tabs
- Home / End jumps to the first / last enabled tab
- Enter / Space activates the focused tab
- Roving
tabindexkeeps the tab list a single tab stop; disabled tabs are skipped - The sliding indicator and content transitions respect
prefers-reduced-motion