Segment Group
Segment control with an animated sliding indicator for single selection; collapses to a vertical stack when its row can't fit.
Playground
<SegmentGroup>
<SegmentItem value="list">List</SegmentItem>
<SegmentItem value="grid">Grid</SegmentItem>
<SegmentItem value="board">Board</SegmentItem>
</SegmentGroup>01 Examples
Time-Range Selector
<SegmentGroup value="1h" ariaLabel="Time range">
<SegmentItem value="1h">1H</SegmentItem>
<SegmentItem value="6h">6H</SegmentItem>
<SegmentItem value="1d">1D</SegmentItem>
<SegmentItem value="1w">1W</SegmentItem>
<SegmentItem value="1m">1M</SegmentItem>
</SegmentGroup>Per-Item Disabled
<SegmentGroup value="a" ariaLabel="Partial disabled example">
<SegmentItem value="a">Available</SegmentItem>
<SegmentItem value="b" disabled>Unavailable</SegmentItem>
<SegmentItem value="c">Available</SegmentItem>
</SegmentGroup>Theme Switcher
<div
class="border-border-subtle bg-surface-elevated flex w-full max-w-sm items-center justify-between rounded-2xl border p-4"
>
<span class="text-text-primary text-sm font-medium">Appearance</span>
<SegmentGroup bind:value={theme} size="sm" mint="scale" ariaLabel="Theme preference">
<SegmentItem value="light">Light</SegmentItem>
<SegmentItem value="dark">Dark</SegmentItem>
<SegmentItem value="system">System</SegmentItem>
</SegmentGroup>
</div>02 Micro-Interactions
Configured Mint
<SegmentGroup
value="pro"
mint={[{ name: 'scale', config: { intensity: 1.03, duration: 200 } }, 'glow']}
ariaLabel="Configured mint"
>
<SegmentItem value="free">Free</SegmentItem>
<SegmentItem value="pro">Pro</SegmentItem>
<SegmentItem value="enterprise">Enterprise</SegmentItem>
</SegmentGroup>03 Choosing the Right Component
SegmentGroup
Compact mode/view switcher with animated sliding indicator. Best for 2-5 mutually exclusive options that don't control content panels. Minimal API, single neutral style.
ButtonGroup selection="single"
Toolbar-style toggle with full button styling (variants, intents, connected borders). Choose this when you need visual customization, multi-select, or connected button layouts.
RadioGroup
Form input with labels, descriptions, helper/error text, and native <input type="radio">. Choose this when
collecting data in forms or when options need descriptions.
Tab
Content panel navigation where each option reveals a different panel. Uses role="tablist" semantics. Choose this when switching between
content sections, not selecting a value.
04 Customization
Gradient Pricing Toggle
<SegmentGroup
bind:value={pricing}
mint={['scale', 'glow']}
slotClasses={{
base: 'bg-linear-to-r from-violet-500/15 to-fuchsia-500/15 border border-violet-500/20',
indicator: 'bg-linear-to-r from-violet-600 to-fuchsia-500 shadow-lg shadow-violet-500/30',
item: 'text-violet-300 data-[state=active]:text-white'
}}
ariaLabel="Pricing tier"
>
<SegmentItem value="free">Free</SegmentItem>
<SegmentItem value="pro">Pro</SegmentItem>
<SegmentItem value="enterprise">Enterprise</SegmentItem>
</SegmentGroup>Neon Chart Switcher
<SegmentGroup
bind:value={chartType}
mint="scale"
slotClasses={{
base: 'bg-neutral-900 border border-emerald-500/20',
indicator:
'bg-emerald-500/20 shadow-[0_0_15px_rgba(52,211,153,0.25)] border border-emerald-400/40',
item: 'text-neutral-500 data-[state=active]:text-emerald-400'
}}
ariaLabel="Chart type"
>
<SegmentItem value="line">Line</SegmentItem>
<SegmentItem value="bar">Bar</SegmentItem>
<SegmentItem value="area">Area</SegmentItem>
</SegmentGroup>Glassmorphism
<SegmentGroup
value="overview"
mint="scale"
slotClasses={{
base: 'bg-white/10 backdrop-blur-md border border-white/20 shadow-lg',
indicator: 'bg-white/25 backdrop-blur-sm shadow-lg',
item: 'text-white/60 data-[state=active]:text-white'
}}
ariaLabel="Glass navigation"
>
<SegmentItem value="overview">Overview</SegmentItem>
<SegmentItem value="details">Details</SegmentItem>
<SegmentItem value="history">History</SegmentItem>
</SegmentGroup>Fully Custom (unstyled)
<SegmentGroup
value="bold"
unstyled
mint="scale"
class="inline-flex gap-1 rounded-2xl bg-linear-to-br from-amber-200 to-orange-400 p-1.5 shadow-xl"
slotClasses={{
indicator: 'rounded-xl bg-white/80 shadow-md',
item: 'relative z-[var(--z-docked)] rounded-xl px-5 py-2 text-sm font-bold text-orange-950/80 transition-colors data-[state=active]:text-orange-900'
}}
ariaLabel="Unstyled warm"
>
<SegmentItem value="bold">Bold</SegmentItem>
<SegmentItem value="vibrant">Vibrant</SegmentItem>
<SegmentItem value="muted">Muted</SegmentItem>
</SegmentGroup>
<SegmentGroup
value="deploy"
unstyled
class="inline-flex gap-0 border-2 border-current p-0 font-mono text-sm"
slotClasses={{
indicator: 'bg-text-primary',
item: 'text-text-primary relative z-[var(--z-docked)] border-r border-current px-5 py-2.5 font-bold tracking-widest uppercase transition-colors last:border-r-0 data-[state=active]:text-surface-base'
}}
ariaLabel="Unstyled brutalist"
>
<SegmentItem value="staging">Staging</SegmentItem>
<SegmentItem value="deploy">Deploy</SegmentItem>
<SegmentItem value="rollback">Rollback</SegmentItem>
</SegmentGroup>A switcher chrome reused across settings panels belongs in BlocksProvider presets (presets.SegmentGroup, item styling under presets.SegmentItem) — apply with preset instead of repeating slotClasses. See Customization.
05 Accessibility
ARIA
The container uses role="radiogroup" with each item
as role="radio" + aria-checked. Provide ariaLabel to describe the group's purpose.
Keyboard
Arrow keys move between options and select immediately. Home / End jump to first/last option. Only the active item is in the tab order (roving tabindex).
Visual States
Active items expose data-state="active" for CSS-only styling in unstyled mode. The sliding indicator uses aria-hidden="true" since it is purely decorative.
Focus rings use focus-visible: for keyboard-only visibility.
06 API Reference
Prop | Type | Default | Description | |
|---|---|---|---|---|
ariaLabel | string | — | Accessible label for the segment group. | |
children | Snippet | — | Segment items to render. Must be SegmentItem components. | |
class | string | — | Extra classes merged onto the root element. | |
collapseOnOverflow | boolean | true | When the segments can't fit their available width, collapse the horizontal
track to a vertical radio-style stack (all options stay visible) instead of
overflowing. Triggered by real measured overflow (ResizeObserver), not a
viewport breakpoint, so it only engages when an instance genuinely doesn't
fit — a 2-segment switcher that fits stays horizontal. Set false to keep
the track horizontal (it still won't push the page wider than its parent). | |
disabled | boolean | false | Prevent interaction and dim the control. | |
fullWidth variant | true | false | Controls the fullWidth behavior and appearance of the SegmentGroup component. Available options: true. | |
mint | MintProp | 'none' | Micro-interaction preset applied to each segment item (per-item via context). Only applies while the item is not disabled. Accepts a preset name, an array of names, or configured mint objects. | |
onValueChange | (value: string) => void | — | Fires after the selected value changes. Receives the new value. | |
preset | string | — | Apply a named preset registered via <BlocksProvider presets={{ SegmentGroup: {...} }}>.
Prefer this over class overrides when the requested look falls outside the
semantic intent palette — presets keep hover/active/dark-mode logic coherent
and make the custom look reusable across the project. | |
size variant | lgmdsm | md | Controls the dimensions, padding, and text size of the SegmentGroup. Affects the component's physical footprint. Available options: lg, md, sm. | |
slotClasses | Partial<Record<SegmentGroupSlots, string>> | — | Per-slot class overrides merged with tv() styles. | |
tier variant | commitmodify | commit | Selects the semantic radius tier of the SegmentGroup — the shape family it belongs to (--radius-commit/-modify/-contain/-bridge). Shape is retuned per family in your theme, so this picks the family rather than a pixel value. Available options: commit, modify. | |
unstyled | boolean | — | Remove all default tv() classes. | |
value | string | — | Currently selected value. Supports bind:value for two-way binding. | |
variant variant | defaulttext | default | Controls the visual style and presentation of the SegmentGroup. Determines the component's visual treatment. Available options: default, text. | |
...HTMLAttributes<HTMLDivElement> inherited | HTMLAttributes | — | HTML attributes (excluding: 'children') | |
...SegmentGroupVariants variant | VariantProps | — | Styling variants from SegmentGroupVariants |
07 Types
Local type definitions used by this component.
Name | Kind | Category | Used by | Description | |
|---|---|---|---|---|---|
SegmentGroupContext | interface | helper | 0 | Reactive context exposed to child SegmentItem components. | |
SegmentGroupProps | interface | props | 0 | — | |
SegmentItemProps | interface | props | 0 | Individual option inside a SegmentGroup. | |
SegmentGroupVariants | type | variant | 1 | — | |
SegmentGroupSlots | type | variant | 0 | Slot names derived from the tv() config above — single source of truth for slotClasses. | |
MintProp | type | helper | 1 | — | |
InteractiveTier | type | helper | 0 | Semantic radius tier for interactive surfaces (3-tier system).
- commit → r-human (CTA, identity, status declarations)
- modify → r-interactive (fields, navigation, secondary actions)
Container components (Card, Alert, Toolbar surface, …) live in a third
tier contain (r-structure) which is **not** part of this propagation
context — those surfaces are always r-structure by design and have no
tier-flip use case. | |
MintName | type | helper | 0 | A mint name: a built-in (autocompleted), 'none' to disable, or any
consumer-registered name. (string & {}) keeps the registry open — a
custom name still type-checks, it just isn't suggested. A typo therefore
also still compiles (it resolves like an unregistered custom name and
warns at runtime); the union buys completion and docs, not validation. | |
MintConfig | interface | helper | 0 | — | |
BuiltinMintName | type | helper | 0 | Built-in mint names as a literal union, so the mint prop autocompletes
across every component — the single list the hand-curated playground knobs
and docs used to drift away from. |
08 Installation
Import
import { SegmentGroup, SegmentItem } from '@urbicon-ui/blocks';