Tabs
Switchable panels with an animated indicator, five variants and full keyboard support.
The line variant slides its indicator between triggers.
"use client";
import { Tabs, Text, Box } from "astralis-ui";
export function TabsDemo() {
return (
<Box w="full" maxW="md">
<Tabs defaultValue="overview">
<Tabs.List>
<Tabs.Trigger value="overview">Overview</Tabs.Trigger>
<Tabs.Trigger value="activity">Activity</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
</Tabs.List>
<Box pt="4">
<Tabs.Content value="overview">
<Text size="sm" color="muted">
The line variant slides its indicator between triggers.
</Text>
</Tabs.Content>
<Tabs.Content value="activity">
<Text size="sm" color="muted">Recent activity shows here.</Text>
</Tabs.Content>
<Tabs.Content value="settings">
<Text size="sm" color="muted">Settings panel.</Text>
</Tabs.Content>
</Box>
</Tabs>
</Box>
);
}Import#
import { Tabs } from "astralis-ui";
// Tabs.List, Tabs.Trigger, Tabs.ContentVariants#
line slides a measured indicator under the active trigger; segmented
raises it on a track; outline gives folder tabs. Individual triggers can
be disabled.
line
subtle
segmented
outline
plain
"use client";
import { Tabs, Text, VStack } from "astralis-ui";
const variants = ["line", "subtle", "segmented", "outline", "plain"] as const;
export function TabsVariants() {
return (
<VStack gap="5" alignItems="start">
{variants.map((variant) => (
<VStack key={variant} gap="1" alignItems="start">
<Text as="span" size="xs" color="muted">{variant}</Text>
<Tabs variant={variant} size="sm" defaultValue="one">
<Tabs.List>
<Tabs.Trigger value="one">First</Tabs.Trigger>
<Tabs.Trigger value="two">Second</Tabs.Trigger>
<Tabs.Trigger value="three" disabled>Disabled</Tabs.Trigger>
</Tabs.List>
</Tabs>
</VStack>
))}
</VStack>
);
}Vertical#
orientation="vertical" stacks the list and flips the arrow-key axis — the
settings-page classic.
Name, avatar, bio.
"use client";
import { Tabs, Text, HStack, Box } from "astralis-ui";
export function TabsVertical() {
return (
<Tabs orientation="vertical" variant="subtle" defaultValue="profile">
<HStack gap="6" alignItems="start">
<Tabs.List>
<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
<Tabs.Trigger value="security">Security</Tabs.Trigger>
<Tabs.Trigger value="billing">Billing</Tabs.Trigger>
</Tabs.List>
<Box minW="3xs">
<Tabs.Content value="profile">
<Text size="sm" color="muted">Name, avatar, bio.</Text>
</Tabs.Content>
<Tabs.Content value="security">
<Text size="sm" color="muted">Password and sessions.</Text>
</Tabs.Content>
<Tabs.Content value="billing">
<Text size="sm" color="muted">Plan and invoices.</Text>
</Tabs.Content>
</Box>
</HStack>
</Tabs>
);
}Fitted and rounded#
fitted stretches triggers across the list; with segmented + rounded it
becomes the familiar pill switcher.
"use client";
import { Tabs, Box } from "astralis-ui";
export function TabsFitted() {
return (
/* fitted stretches triggers across the list; rounded makes it a pill. */
<Box w="full" maxW="sm">
<Tabs variant="segmented" fitted rounded defaultValue="day">
<Tabs.List>
<Tabs.Trigger value="day">Day</Tabs.Trigger>
<Tabs.Trigger value="week">Week</Tabs.Trigger>
<Tabs.Trigger value="month">Month</Tabs.Trigger>
</Tabs.List>
</Tabs>
</Box>
);
}Props#
| Prop | Type | Default | Description |
|---|---|---|---|
value · defaultValue · onValueChange | string · string · (value: string) => void | — | Controlled / initial tab, and the change callback. |
variant | "line" | "subtle" | "segmented" | "outline" | "plain" | "line" | Underline with sliding indicator, filled pill, raised track, folder tabs, or minimal. |
colorScheme | ColorScheme | "brand" | Hue for the active tab, sliding indicator, and focus rings (via the accent channel). |
size | "sm" | "md" | "lg" | "md" | Trigger padding and text size. |
orientation | "horizontal" | "vertical" | "horizontal" | Layout and arrow-key axis. |
fitted | boolean | false | Stretch triggers to fill the list width. |
rounded | boolean | false | Pill radii for the subtle/segmented/outline variants. |
activationMode | "automatic" | "manual" | "automatic" | Whether arrow-key focus selects immediately, or Enter/Space confirms. |
keepMounted | boolean | false | Hide inactive panels instead of unmounting them. |
loop | boolean | true | Arrow-key focus wraps around the ends. |
Parts#
| Prop | Type | Default | Description |
|---|---|---|---|
Tabs.Trigger | value: string · disabled?: boolean | — | One tab button; value links it to its panel. |
Tabs.Content | value: string | — | The panel shown while its value is active. |
Keyboard#
| Key | Action |
|---|---|
← / → | Move focus between tabs (↑ / ↓ when vertical); wraps with loop |
Home / End | First / last tab |
Enter / Space | Select the focused tab |
With activationMode="automatic" (the default), moving focus also selects.
With "manual", focus moves freely and Enter/Space commits — use it for
panels that are expensive to switch.
Accessibility#
- Real
tablist/tab/tabpanelroles witharia-selectedandaria-controlswiring. - Roving tabindex: one Tab stop for the whole list, arrows move within it.