Tabs
A tabbed interface for organizing content into separate views.
Anatomy
<script>
import { Tabs } from "@saas-ui/svelte/components/tabs";
</script>
<Tabs.Root defaultValue="tab1">
<Tabs.List>
<Tabs.Trigger value="tab1">Tab 1</Tabs.Trigger>
<Tabs.Trigger value="tab2">Tab 2</Tabs.Trigger>
<Tabs.Trigger value="tab3">Tab 3</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="tab1">Content for Tab 1</Tabs.Content>
<Tabs.Content value="tab2">Content for Tab 2</Tabs.Content>
<Tabs.Content value="tab3">Content for Tab 3</Tabs.Content>
</Tabs.Root>Examples
Basic Accessibility
<Tabs.Root defaultValue="members">
<Tabs.List>
<Tabs.Trigger value="members">
<Icon as={User} size="xs" />
Members
</Tabs.Trigger>
<Tabs.Trigger value="projects">
<Icon as={Folder} size="xs" />
Projects
</Tabs.Trigger>
<Tabs.Trigger value="settings">
<Icon as={CheckSquare} size="xs" />
Settings
</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="members">Manage your team members</Tabs.Content>
<Tabs.Content value="projects">Manage your projects</Tabs.Content>
<Tabs.Content value="settings">Manage your settings</Tabs.Content>
</Tabs.Root> Colours Accessibility
colour prop to change the colour theme.
<VStack gap={6}>
{#each colours as colour}
<HStack gap={4} class="items-start">
<Text size="xs" class="w-16 pt-2 capitalize">{colour}</Text>
<VStack gap={2}>
<Text size="xs" class="text-fg-muted">line</Text>
<Tabs.Root
defaultValue="tab-1"
variant="line"
{colour}
size="sm"
>
<Tabs.List>
<Tabs.Trigger value="tab-1">Tab 1</Tabs.Trigger>
<Tabs.Trigger value="tab-2">Tab 2</Tabs.Trigger>
<Tabs.Trigger value="tab-3">Tab 3</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="tab-1" class="sr-only"
>Tab 1</Tabs.Content>
<Tabs.Content value="tab-2" class="sr-only"
>Tab 2</Tabs.Content>
<Tabs.Content value="tab-3" class="sr-only"
>Tab 3</Tabs.Content>
</Tabs.Root>
</VStack>
<VStack gap={2}>
<Text size="xs" class="text-fg-muted">subtle</Text>
<Tabs.Root
defaultValue="tab-1"
variant="subtle"
{colour}
size="sm"
>
<Tabs.List>
<Tabs.Trigger value="tab-1">Tab 1</Tabs.Trigger>
<Tabs.Trigger value="tab-2">Tab 2</Tabs.Trigger>
<Tabs.Trigger value="tab-3">Tab 3</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="tab-1" class="sr-only"
>Tab 1</Tabs.Content>
<Tabs.Content value="tab-2" class="sr-only"
>Tab 2</Tabs.Content>
<Tabs.Content value="tab-3" class="sr-only"
>Tab 3</Tabs.Content>
</Tabs.Root>
</VStack>
<VStack gap={2}>
<Text size="xs" class="text-fg-muted">outline</Text>
<Tabs.Root
defaultValue="tab-1"
variant="outline"
{colour}
size="sm"
>
<Tabs.List>
<Tabs.Trigger value="tab-1">Tab 1</Tabs.Trigger>
<Tabs.Trigger value="tab-2">Tab 2</Tabs.Trigger>
<Tabs.Trigger value="tab-3">Tab 3</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="tab-1" class="sr-only"
>Tab 1</Tabs.Content>
<Tabs.Content value="tab-2" class="sr-only"
>Tab 2</Tabs.Content>
<Tabs.Content value="tab-3" class="sr-only"
>Tab 3</Tabs.Content>
</Tabs.Root>
</VStack>
</HStack>
{/each}
</VStack> Disabled Tab Accessibility
disabled prop on individual triggers.
<Tabs.Root defaultValue="members">
<Tabs.List>
<Tabs.Trigger value="members">
<Icon as={User} size="xs" />
Members
</Tabs.Trigger>
<Tabs.Trigger value="projects" disabled>
<Icon as={Folder} size="xs" />
Projects
</Tabs.Trigger>
<Tabs.Trigger value="settings">
<Icon as={CheckSquare} size="xs" />
Settings
</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="members">Manage your team members</Tabs.Content>
<Tabs.Content value="projects">Manage your projects</Tabs.Content>
<Tabs.Content value="settings">Manage your settings</Tabs.Content>
</Tabs.Root> Dynamic Accessibility
<Tabs.Root
value={selectedTab}
variant="enclosed"
size="sm"
onValueChange={(e) => (selectedTab = e.value)}
>
<HStack gap={2} class="items-center">
<Tabs.List class="flex-1">
{#each dynamicTabs as tab (tab.id)}
<Tabs.Trigger value={tab.id}>
{tab.title}
</Tabs.Trigger>
{/each}
</Tabs.List>
<Button size="2xs" variant="ghost" onclick={addTab}>
<Icon as={Plus} size="xs" />
Add Tab
</Button>
</HStack>
<Tabs.ContentGroup>
{#each dynamicTabs as tab (tab.id)}
<Tabs.Content value={tab.id}>
<HStack class="items-start justify-between">
<Text size="lg" class="my-4 font-semibold">
{tab.content}
{tab.id}
</Text>
{#if dynamicTabs.length> 1}
<CloseButton
size="xs"
aria-label="Close tab {tab.id}"
onclick={() => removeTab(tab.id)}
/> Fitted Accessibility
fitted prop to make tabs fill the container width.
<Tabs.Root variant="outline" fitted defaultValue="tab-1" class="max-w-md">
<Tabs.List>
<Tabs.Trigger value="tab-1" class="whitespace-nowrap"
>Tab 1</Tabs.Trigger>
<Tabs.Trigger value="tab-2" class="whitespace-nowrap"
>Tab 2</Tabs.Trigger>
<Tabs.Trigger value="tab-3" class="whitespace-nowrap"
>Tab 3</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="tab-1">Tab 1 content</Tabs.Content>
<Tabs.Content value="tab-2">Tab 2 content</Tabs.Content>
<Tabs.Content value="tab-3">Tab 3 content</Tabs.Content>
</Tabs.Root> Lazy Mounted Accessibility
lazyMount and unmountOnExit for performance optimization.
<Tabs.Root lazyMount unmountOnExit defaultValue="tab-1">
<Tabs.List>
<Tabs.Trigger value="tab-1">Tab 1</Tabs.Trigger>
<Tabs.Trigger value="tab-2">Tab 2</Tabs.Trigger>
<Tabs.Trigger value="tab-3">Tab 3</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="tab-1">Tab 1: Content (lazy mounted)</Tabs.Content>
<Tabs.Content value="tab-2">Tab 2: Content (lazy mounted)</Tabs.Content>
<Tabs.Content value="tab-3">Tab 3: Content (lazy mounted)</Tabs.Content>
</Tabs.Root> Manual Activation Accessibility
activationMode="manual" to require Enter/click to activate.
<Tabs.Root defaultValue="members" activationMode="manual">
<Tabs.List>
<Tabs.Trigger value="members">Members</Tabs.Trigger>
<Tabs.Trigger value="projects">Projects</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="members">
Manage your team members (click or press Enter to activate)
</Tabs.Content>
<Tabs.Content value="projects">
Manage your projects (click or press Enter to activate)
</Tabs.Content>
<Tabs.Content value="settings">
Manage your settings (click or press Enter to activate)
</Tabs.Content>
</Tabs.Root> Prefetch Accessibility
onPrefetch to lazy-load components when hovering over tabs. This is useful for deferring heavy dependencies (like charts) until the user shows intent to view that tab.
<VStack gap={4}>
<Text size="sm" class="text-fg-muted">
Hover over tabs to prefetch content. The "Heavy" tab simulates
lazy-loading a component.
</Text>
<Tabs.Root defaultValue="light" onPrefetch={handlePrefetch}>
<Tabs.List>
<Tabs.Trigger value="light">Light</Tabs.Trigger>
<Tabs.Trigger value="medium">Medium</Tabs.Trigger>
<Tabs.Trigger value="heavy">Heavy (Lazy)</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="light">
<Text>This content is always available.</Text>
</Tabs.Content>
<Tabs.Content value="medium">
<Text>This content is also lightweight.</Text>
</Tabs.Content>
<Tabs.Content value="heavy">
{#if HeavyComponent}
<Text>Heavy component loaded! This could be a chart,
dashboard, or any large module.</Text> Sizes Accessibility
size prop to change the size of the tabs.
<VStack gap={10} class="w-full">
{#each tabsSizes as size}
<VStack gap={2}>
<Text size="xs" class="text-fg-muted capitalize">{size}</Text>
<Tabs.Root {size} defaultValue="members">
<Tabs.List>
<Tabs.Trigger value="members">Members</Tabs.Trigger>
<Tabs.Trigger value="projects">Projects</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="members"
>Manage your team members</Tabs.Content>
<Tabs.Content value="projects"
>Manage your projects</Tabs.Content>
<Tabs.Content value="settings"
>Manage your settings</Tabs.Content>
</Tabs.Root>
</VStack>
{/each}
</VStack> Variants Accessibility
variant prop to change the visual style.
<VStack gap={10} class="w-full">
{#each tabsVariants as variant}
<VStack gap={2}>
<Text size="xs" class="text-fg-muted capitalize">{variant}</Text>
<Tabs.Root {variant} defaultValue="members">
<Tabs.List>
<Tabs.Trigger value="members">
<Icon as={User} size="xs" />
Members
</Tabs.Trigger>
<Tabs.Trigger value="projects">
<Icon as={Folder} size="xs" />
Projects
</Tabs.Trigger>
<Tabs.Trigger value="settings">
<Icon as={CheckSquare} size="xs" />
Settings
</Tabs.Trigger>
{#if variant === "outline"}
<Tabs.Indicator /> With Indicator Accessibility
Tabs.Indicator for a custom animated indicator.
<Tabs.Root defaultValue="members" variant="plain">
<Tabs.List class="bg-bg-muted rounded-lg p-1">
<Tabs.Trigger value="members">
<Icon as={User} size="xs" />
Members
</Tabs.Trigger>
<Tabs.Trigger value="projects">
<Icon as={Folder} size="xs" />
Projects
</Tabs.Trigger>
<Tabs.Trigger value="settings">
<Icon as={CheckSquare} size="xs" />
Settings
</Tabs.Trigger>
<Tabs.Indicator class="bg-bg-default rounded-md shadow" />
</Tabs.List>
<Tabs.Content value="members">Manage your team members</Tabs.Content>
<Tabs.Content value="projects">Manage your projects</Tabs.Content>
<Tabs.Content value="settings">Manage your settings</Tabs.Content>
</Tabs.Root>Props
Tabs.Root
The root container component for the tabs.
| Prop | Type | Default | Description |
|---|---|---|---|
children | Snippet | - | Content to render inside the tabs. |
class | string | - | Additional classes to apply. |
defaultValue | string | - | The default value of the selected tab. |
value | string | - | The controlled value of the selected tab. |
activationMode | "automatic" | "manual" | "automatic" | The activation mode of the tabs. |
lazyMount | boolean | false | Whether to enable lazy mounting. |
unmountOnExit | boolean | false | Whether to unmount tab content when not active. |
orientation | "horizontal" | "vertical" | "horizontal" | The orientation of the tabs. |
loopFocus | boolean | true | Whether the keyboard navigation will loop. |
size | "xs" | "sm" | "md" | "lg" | "md" | The size of the tabs. |
variant | "line" | "subtle" | "enclosed" | "outline" | "plain" | "line" | The visual style of the tabs. |
fitted | boolean | false | Whether tabs should stretch to fill the container. |
justify | "start" | "center" | "end" | - | The alignment of the tabs. |
colour | ColourName | "gray" | The colour palette of the tabs. |
onValueChange | (details: { value: string }) => void | - | Callback when the selected tab changes. |
onPrefetch | (value: string) => void | - | Callback invoked when hovering over a tab trigger. Similar to Astro's link prefetching, this allows preloading data before selection. |
Tabs.List
The container for the tab triggers.
| Prop | Type | Default | Description |
|---|---|---|---|
children | Snippet | - | The content of the tabs list. |
class | string | - | Additional classes to apply. |
Tabs.Trigger
The clickable tab trigger that activates the corresponding content.
| Prop | Type | Default | Description |
|---|---|---|---|
children | Snippet | - | The content of the trigger. |
value | string | - | The unique value of the tab. |
disabled | boolean | false | Whether the tab is disabled. |
class | string | - | Additional classes to apply. |
Tabs.Content
The panel content associated with a tab trigger.
| Prop | Type | Default | Description |
|---|---|---|---|
children | Snippet | - | The content of the tab panel. |
value | string | - | The unique value of the tab this content belongs to. |
class | string | - | Additional classes to apply. |
Tabs.ContentGroup
A container for grouping tab content panels.
| Prop | Type | Default | Description |
|---|---|---|---|
children | Snippet | - | The content (tab panels) to render. |
class | string | - | Additional classes to apply. |
Tabs.Indicator
An animated indicator that highlights the active tab.
| Prop | Type | Default | Description |
|---|---|---|---|
class | string | - | Additional classes to apply. |