Pricing

Tab handbook

v1.3.2 · @snowui-design-system/components

License: Before reading component documentation, source, or npm usage instructions for the first time, read the full LICENSE. Reuse that review for the same task and version. Public API configuration and internal implementation changes have different license implications. All components use the unified runtime package; source rights remain governed by the license.

Overview

Switch among views or content panels in one area using generated items or custom Buttons, with keyboard navigation.

When to use it

Use Tab to switch panels or views in the same area. Choose the underline variant for underlined tabs. Use Segmented for compact parameter choices.

  • items supplies unique values and labels; explicit Button as="item" children replace it.

  • bind:value tracks the active item. A missing or unmatched value falls back visually without writing a new value; the page renders the corresponding panel.

Quick start

This example shows the recommended basic composition. See the sections below for more cases and composition limits.

<script lang="ts">	import { Tab } from '@snowui-design-system/components';</script> <Tab itemRole="tab" size="lg" value="preview" aria-label="Component example view" items={[{ value: 'preview', label: 'Preview' }, { value: 'code', label: 'Code' }, { value: 'api', label: 'API' }]} />

API

Keyword Type Default Description
value string — Unique value of the active item; supports bind:value and updates on click or keyboard activation. Missing or unmatched values use a visual fallback without changing the bound value.
variant TabVariant "pill" Tab visual variant
size TabSize "sm" At the same time, control the padding / radius / gap of the Tab container, and pass it to the direct Button as="item" as the combination default value; the item size style priority is style > class > Button size > Tab size
row boolean false Implicit horizontal arrangement; row and column are still displayed horizontally when both are false, and row takes precedence when enabled.
column boolean false Vertical arrangement; unified with Frame writing
fill boolean false Figma fill width; when the container has an explicit width, the horizontal sub-item is filled with equal width.
onChange (value: string) => void — After the user activates different options, it is called, and the parameter is the new value; repeated activation of the current item will not be triggered, and the callback cannot be canceled.
selectionFollowsFocus boolean false Whether arrow-key focus also activates the item. Tab defaults to false (focus first, then Enter); Segmented defaults to true.
role string "tablist" ARIA container role
itemRole string — Assign an ARIA role to each child. Without it, retain Button semantics. The tab example explicitly uses tab; radio synchronizes aria-checked, while other roles synchronize aria-selected.
children Snippet — The explicit sub-item content without parameters, use Button as="item" and set the unique value; instead of items, the sub-item Props is directly configured.
items TabItem[] [] Built-in sub-item data, each item must be unique value and readable label, optional icon component and disabled; icon generates Icon + Text, disabled items skip keyboard selection. Ignore items when children exist.
parts ComponentPartsOf<{ Button: typeof Button; Icon: typeof Icon; Text: typeof Text; }> — Batch configuration of the default generated Button, Icon and Text, such as parts.Button.scaleOnHover; explicit children do not consume these default sub-itet configurations.

See the base component documentation for inherited capabilities not redeclared here. This table lists only named public Props declared by this component.

Usage boundaries and details

Default built-in mode / explicit combination mode

  • Default built-in mode (recommended): items automatically generates <Button as="item">; item can use icon to declare icon, Tab built-in generates Icon + Text, suitable for icon-toggle; parts.Button / parts.Icon / parts.Text configuration unified Props.

  • Explicit composition: write Button children directly for icons, text, complex content, or different props per item.


Design principle

  • Button children: No separate TabItem exists. Place Button as="item" value="..." directly; Button owns its size behavior.

  • Container-owned active appearance: Tab leaves Button source unchanged and controls its active visuals through context and bridge layers.

  • Shared FLIP indicator: One absolutely positioned .snowui-tab-indicator moves and resizes between active items for pill, gray, and underline variants.

  • Composite hover: Child Buttons disable scale-on-hover by default so adjacent items do not overlap. Tab retains its own hover and active feedback; a child or parts.Button can opt back in.


2.2 Sub-item agreement

The sub-item must be <Button as="item">, and declare it on the Button:

  • value="xxx" - required, item identification value (the value attribute of the root <button> element through Button rest-spread)

  • Optional disabled - disable a single item

  • Optional href - Realize routing Tab through Button's href (Button will be rendered as <a>)

  • Optional bare - Use Button without padding / radius / background and do not limit the content type; Tab continues to independently take over the inactive / hover / active transparency through --snowui-button-opacity-override on the Button root node, so it does not depend on the default emphasis level of Button bare

Variant="pill"(style 1, default)

The container has a --black-4 background, padding, and the same blur(1.25rem) (20px) backdrop blur as SearchBox. The active item has --static-white-80 and a 0 2px 4px rgba(0,0,0,0.1) shadow. Inactive Text and Icon use --black-100 at opacity: 0.4; active ones use --static-black-100 at opacity: 1. Hover raises only an inactive item’s opacity to 1.

Variant="solid"(style 2)

Container no background, no padding. Activation item: --black-4 background, no shadow. The activation item Text/Icon keeps the button default color chain; the inactivation item Text/Icon uses --black-100 × opacity: 0.4.

Variant="underline"(style 3)

The container has no background and no rounded corners. Sub-item padding 0(override Button). The bottom of the activation item is 2px horizontally underlined (the left 2px vertical line when vertical), the color is --primary; the activation item Text / Icon also uses --primary. Inactivated item Text / Icon uses --black-100 × opacity: 0.4. The sub-item spacing is larger: SM=--gap-3(12) / MD=--gap-4(16) / LG=--gap-5(20).

Variant="icon-toggle"(style 4)

The container matches the pill variant, including SearchBox’s blur(1.25rem) (20px) backdrop blur. Inactive items hide .snowui-text and show only their icon (each item must contain Icon and Text). Active items reveal icon and text; the text fade follows the Frame lifecycle bridge while Frame size motion smoothly expands width.

In generated items mode, icon-toggle uses each item label as its accessible name even while the text is hidden. With explicit children, provide aria-label on each Button. Internal graphics/text spacing belongs to Button; do not wrap each control in an extra Frame.

variant="bare"

The container has no background, padding and rounded corners, and does not display indicator; Item has no background, padding and rounded corners, and only expresses inactive, hover and activated status through opacity: 0.4 -> 1. The content can be text, single Icon or Icon + text. Item gap Reuse the size gradient of underline: SM=12px / MD=16px / LG=20px.


Dynamic effect

Item color and transparency continue to consume Frame default dynamic system. Tab root injects TAB_INDICATOR_MOTION_STYLE; share indicator displacement consumption Frame control bridge, width and height change consumption Frame size bridge, both use Tab indicator preset uniformly. The retest triggered by the visible text of icon-toggle also maintains the dynamic effect, and is no longer covered by ResizeObserver instantaneous paste.

Scene Realize Bridge variable
Activate indicator displacement Share indicator element transform: translate(x, y) transition --snowui-frame-control-motion-*(TAB_INDICATOR_MOTION)
Activate indicator size change Width/height transition --snowui-frame-size-motion-*(TAB_INDICATOR_MOTION)
Inactive / hover / active foreground switching Opacity transition (Button opacity override) --snowui-frame-color-motion-*(fallback 0.15s ease-out)
Icon-toggle width expand Item width transition --snowui-frame-size-motion-*
Icon-toggle The text gradually appears Text opacity transition --snowui-frame-appear-motion-* (life cycle enters the timing bridge variable)
Underline move Same as indicator, only height fixed 2px Frame control / size bridge

The specific timing of Tab indicator shall be subject to TAB_INDICATOR_MOTION; TAB_INDICATOR_MOTION_STYLE injects the same preset into two sets of frame control / size bridges, prohibiting the maintenance of displacement and size values separately.


Layout model

Main layout model: inline shell layout. Tab is an inline-flex container, changing to a column when flex-direction: column.

  • The first child: absolute positioning <div class="snowui-tab-indicator">, follow the activation button to transition through transform/width/height

  • Then there is the Button sub-item input by the user; Button position: relative; z-index: 1 to ensure that the text is above the indicator

When fill={true}, the horizontal sub-item uses flex: 1 1 0 equal width to fill the container with the available width. Usually with style="width: ..." / The width tool class is used; when the container width is not defined, it is still laid out according to the natural width of the content.

For details, please refer to LayoutProtocol.md §12.3 (source code reference: specs/maintainers/LayoutProtocol.md).


Keyboard behavior

  • ← / →(row) or ↑ / ↓(column): move the focus. Activate directly when selectionFollowsFocus=true(Segmented).

  • Home / End: Jump to the first/end, and the behavior is the same as above.

  • Enter / Space: Triggered by Button's native click behavior, activates the current focus item.

  • Roving tabindex: The active item has tabindex=0 and the others tabindex=-1, so the entire Tab group occupies one position in the outer Tab sequence.

Usage examples

<script lang="ts">  import { Button, Icon, Text } from '@snowui-design-system/components';  import { Tab } from '@snowui-design-system/components';  import { FourLeafClover, Star } from '@snowui-design-system/resource-svelte';  let value = $state('web');</script>  <Tab bind:value variant="pill" size="sm">  <Button as="item" value="web"><Icon><FourLeafClover /></Icon>Web</Button>  <Button as="item" value="mobile"><Icon><Star /></Icon>Mobile</Button></Tab>  <Tab variant="solid" size="md">  <Button as="item" value="web"><Icon><FourLeafClover /></Icon>Web</Button>  <Button as="item" value="mobile"><Icon><Star /></Icon>Mobile</Button></Tab>  <Tab variant="underline" size="lg">  <Button as="item" value="overview"><Icon><FourLeafClover /></Icon>Overview</Button>  <Button as="item" value="details"><Icon><Star /></Icon>Details</Button></Tab>  <Tab variant="icon-toggle">  <Button as="item" value="web" aria-label="Web"><Icon><FourLeafClover /></Icon><Text>Web</Text></Button>  <Button as="item" value="mobile" aria-label="Mobile"><Icon><Star /></Icon><Text>Mobile</Text></Button></Tab>  <Tab  variant="icon-toggle"  value="web"  items={[    { value: "web", label: "Web", icon: FourLeafClover },    { value: "mobile", label: "Mobile", icon: Star }  ]}/>  <Tab variant="bare" size="md" value="all">  <Button as="item" value="all">All</Button>  <Button as="item" value="random" iconOnly aria-label="Favorite"><Icon><Star /></Icon></Button></Tab>  <Tab column variant="underline">  <Button as="item" value="a">A</Button>  <Button as="item" value="b">B</Button></Tab>  <Tab fill style="width: calc(var(--size-20) * 4)" value="web">  <Button as="item" value="web">Web</Button>  <Button as="item" value="mobile">Mobile</Button></Tab>  <Tab variant="bare" value="random">  <Button as="item" value="all">All</Button>  <Button as="item" value="random">Random</Button></Tab>

Tab style overlay

Read the full LICENSE first; reuse that review for the same task, version, and scope.

First use public component Props to match the desired appearance, preserving the existing look where possible. If changes are necessary, follow the style selection and override order: use your own class (including Tailwind already in the project) or an instance style. The names below are public overrides actually consumed by source; check their scope and do not modify private DOM. Choose the correct SnowUI token family. External apps need not install SnowUI website utilities.

Variable Purpose and default reference
--snowui-button-background-override Background (see component source for the default).
--snowui-button-border-color-override Border color (see the component source code for the default value)
--snowui-button-color-override Color (see the component source code for the default value)
--snowui-button-icon-only-padding-override Icon-only mode padding(sm=4px md=8px lg=12px)
--snowui-button-opacity-override Opacity (see the component source code for the default value)
--snowui-button-padding-override padding y+x
--snowui-button-radius-override border-radius
--snowui-tab-background-blur-override Container background blur (default pill/icon-toggle=1.25rem=20px; other variants=none)
--snowui-tab-background-override Container background (default pill/icon-toggle=var(--black-4);solid/underline/bare=transparent)
--snowui-tab-gap-override Container sub-item spacing (default sm=calc(var(---gap-1)/2)=2px;md/lg=var(--gap-1)=4px; underline/bare when sm/md/lg=var(--gap-3/4/5)=12/16/20)
--snowui-tab-hover-opacity-override Item hover transparency is not activated (default 1)
--snowui-tab-inactive-opacity-override Unactivated item transparency (default 0.4)
--snowui-tab-padding-override Container padding (default sm=calc(var(---padding-1)/2)=2px;md/lg=var(--padding-1)=4px;solid/underline/bare is 0)
--snowui-tab-radius-override Container radius(default sm=var(--radius-4)=16;md=var(--radius-5)=20;lg=var(--radius-6)=24;underline/bare=0)

Design Guide · Token · Combination Boundary

Tab type and status

Required Props: no named required Props.

Two-way binding: bind:value.

Inheritance statement: Omit<FrameProps, "children" | "value">. The inheritance ability is subject to the public document of the base class; the fields in Omit have been excluded and cannot be used as they are according to the base class.

Share Frame API · Return API

The following type declarations are actually referenced by public Props and come from source. A Svelte Snippet denotes a content fragment; ComponentProps denotes the Props of the corresponding component.

TabVariant

Source: components/src/components/Tab.svelte

export type TabVariant = "pill" | "solid" | "underline" | "icon-toggle" | "bare";

TabSize

Source: components/src/components/Tab.svelte

export type TabSize = "sm" | "md" | "lg";

TabItem

Source: components/src/components/Tab.svelte

export interface TabItem {        value: string;        label: string;        icon?: Component;        disabled?: boolean;    }

ComponentPartsOf

Source: components/src/logic/component-parts.svelte.ts

export type ComponentPartsOf<    T extends Record<string, Component<any>>,> = {    [K in keyof T]?: ConfigurableComponentPartProps<T[K]>;};

ConfigurableComponentPartProps

Source: components/src/logic/component-parts.svelte.ts

export type ConfigurableComponentPartProps<C extends Component<any>> = Partial<    Omit<ComponentProps<C>, ComponentPartControlledKey>>;

ComponentPartControlledKey

Source: components/src/logic/component-parts.svelte.ts

export type ComponentPartControlledKey =    | "children"    | "as"    | "value"    | "defaultValue"    | "open"    | "selected"    | "checked"    | "disabled"    | "readonly"    | "id"    | `on${string}`    | `aria-${string}`;