API
Tabs
Tabbed panels on radios, with keyboard navigation.
Installation
pnpm dlx zazz-ui add tabsnpx zazz-ui add tabsyarn dlx zazz-ui add tabsbunx zazz-ui add tabsAlso adds fields, which it depends on. Run zazz-ui init first if the project has no zazz/ folder yet.
Examples
Account
Manage your profile, email, and password.
Billing
Review invoices and update your payment method.
Notifications
Choose what we email you about and how often.
Account
Manage your profile, email, and password.
Billing
Review invoices and update your payment method.
Notifications
Choose what we email you about and how often.
<!-- Tabs horizontal START -->
<ui-tabs style="--w: 100%">
<div data-tabs-slot="list" role="tablist">
<div data-tabs-slot="indicator" aria-hidden="true"></div>
<input type="radio" role="tab" name="tabs-group-1" id="tab-1" checked />
<label for="tab-1" data-tabs-slot="label">
<span data-tabs-slot="label-text">Account</span>
</label>
<input type="radio" role="tab" name="tabs-group-1" id="tab-2" />
<label for="tab-2" data-tabs-slot="label">
<span data-tabs-slot="label-text">Billing</span>
</label>
<input type="radio" role="tab" name="tabs-group-1" id="tab-3" />
<label for="tab-3" data-tabs-slot="label">
<span data-tabs-slot="label-text">Notifications</span>
</label>
</div>
<div
data-tabs-slot="panel"
role="tabpanel"
style="
--p: 6;
--w: 100%;
--border-color: var(--color-border);
--border-width: 1px;
--rounded: var(--radius-md);
"
>
<h2
style="
--font-size: var(--font-size-lg);
--font-weight: strong;
--leading: var(--leading-lg);
--tracking: var(--tracking-lg);
"
>
Account
</h2>
<p
style="
--mt: 2;
--text: var(--color-muted-foreground);
"
>
Manage your profile, email, and password.
</p>
</div>
<div
data-tabs-slot="panel"
role="tabpanel"
style="
--p: 6;
--w: 100%;
--border-color: var(--color-border);
--border-width: 1px;
--rounded: var(--radius-md);
"
>
<h2
style="
--font-size: var(--font-size-lg);
--font-weight: strong;
--leading: var(--leading-lg);
--tracking: var(--tracking-lg);
"
>
Billing
</h2>
<p
style="
--mt: 2;
--text: var(--color-muted-foreground);
"
>
Review invoices and update your payment method.
</p>
</div>
<div
data-tabs-slot="panel"
role="tabpanel"
style="
--p: 6;
--w: 100%;
--border-color: var(--color-border);
--border-width: 1px;
--rounded: var(--radius-md);
"
>
<h2
style="
--font-size: var(--font-size-lg);
--font-weight: strong;
--leading: var(--leading-lg);
--tracking: var(--tracking-lg);
"
>
Notifications
</h2>
<p
style="
--mt: 2;
--text: var(--color-muted-foreground);
"
>
Choose what we email you about and how often.
</p>
</div>
</ui-tabs>
<!-- Tabs horizontal END -->
<!-- Tabs vertical START -->
<ui-tabs data-tabs-orientation="vertical" style="--w: 100%">
<div data-tabs-slot="list" role="tablist">
<div data-tabs-slot="indicator" aria-hidden="true"></div>
<input type="radio" role="tab" name="tabs-group-2" id="tab-vertical-1" checked />
<label for="tab-vertical-1" data-tabs-slot="label">
<span data-tabs-slot="label-text">Account</span>
</label>
<input type="radio" role="tab" name="tabs-group-2" id="tab-vertical-2" />
<label for="tab-vertical-2" data-tabs-slot="label">
<span data-tabs-slot="label-text">Billing</span>
</label>
<input type="radio" role="tab" name="tabs-group-2" id="tab-vertical-3" />
<label for="tab-vertical-3" data-tabs-slot="label">
<span data-tabs-slot="label-text">Notifications</span>
</label>
</div>
<div
data-tabs-slot="panel"
role="tabpanel"
style="
--p: 6;
--w: 100%;
--border-color: var(--color-border);
--border-width: 1px;
--rounded: var(--radius-md);
"
>
<h2
style="
--font-size: var(--font-size-lg);
--font-weight: strong;
--leading: var(--leading-lg);
--tracking: var(--tracking-lg);
"
>
Account
</h2>
<p
style="
--mt: 2;
--text: var(--color-muted-foreground);
"
>
Manage your profile, email, and password.
</p>
</div>
<div
data-tabs-slot="panel"
role="tabpanel"
style="
--p: 6;
--w: 100%;
--border-color: var(--color-border);
--border-width: 1px;
--rounded: var(--radius-md);
"
>
<h2
style="
--font-size: var(--font-size-lg);
--font-weight: strong;
--leading: var(--leading-lg);
--tracking: var(--tracking-lg);
"
>
Billing
</h2>
<p
style="
--mt: 2;
--text: var(--color-muted-foreground);
"
>
Review invoices and update your payment method.
</p>
</div>
<div
data-tabs-slot="panel"
role="tabpanel"
style="
--p: 6;
--w: 100%;
--border-color: var(--color-border);
--border-width: 1px;
--rounded: var(--radius-md);
"
>
<h2
style="
--font-size: var(--font-size-lg);
--font-weight: strong;
--leading: var(--leading-lg);
--tracking: var(--tracking-lg);
"
>
Notifications
</h2>
<p
style="
--mt: 2;
--text: var(--color-muted-foreground);
"
>
Choose what we email you about and how often.
</p>
</div>
</ui-tabs>
<!-- Tabs vertical END -->/**
* tabs.css — Tabs (ui-tabs | [data-ui~="tabs"]) — CSS-only radio tab panels
*
* @layer variables, components
* @requires _layers.css, _variables.css, fields.css (--ui-field-* metrics)
* @uses input[type="radio"] + :checked — panel visibility via :has() (no JS)
* @uses anchor-name, position-anchor, anchor() — sliding indicator pill
* (@supports gated; checked-label fallback paints card fill without pill)
* @uses anchor-scope — isolates --active-tab inside nested menus
* @uses text-box, color-mix() — label trim and focus ring
* @tokens --ui-tabs-* (@layer variables)
*/
@layer variables {
:root {
/* metrics default to the shared --ui-field-* family so the track matches
adjacent fields and buttons; --ui-tabs-label-min-h is deliberately shorter */
--ui-tabs-gap: var(--space-xs);
--ui-tabs-track-padding: calc(var(--spacing) * 0.5);
--ui-tabs-track-rounded: var(--ui-field-rounded);
--ui-tabs-track-gap: calc(var(--spacing) * 0.5);
--ui-tabs-track-bg: var(--color-muted);
--ui-tabs-indicator-bg: var(--color-card);
--ui-tabs-indicator-rounded: var(--ui-field-inset-child-radius);
--ui-tabs-indicator-shadow: var(--shadow-xs);
--ui-tabs-indicator-inset: var(--ui-tabs-track-padding);
--ui-tabs-label-font-family: var(--font-family-body);
--ui-tabs-label-font-size: var(--font-size-sm);
--ui-tabs-label-font-weight: var(--font-weight-strong);
--ui-tabs-label-leading: var(--leading-sm);
--ui-tabs-label-bg: transparent;
--ui-tabs-label-bg--hover: transparent;
--ui-tabs-label-bg--active: transparent;
--ui-tabs-label-text: var(--color-muted-foreground);
--ui-tabs-label-text--hover: var(--color-foreground);
--ui-tabs-label-text--active: var(--color-foreground);
--ui-tabs-label-px: var(--ui-field-px);
--ui-tabs-label-min-h: calc(var(--spacing) * 7);
--ui-tabs-label-ring-color: var(--ui-field-ring-color);
}
}
@layer ui.components {
/* Panels are direct children after the list; their order must match tab order.
Panel N shows when the Nth radio in the list is checked. */
:where(ui-tabs, [data-ui~="tabs"]) {
display: flex;
flex-direction: column;
align-items: start;
gap: var(--ui-tabs-gap);
}
:where(ui-tabs, [data-ui~="tabs"]) [data-tabs-slot~="list"] {
position: relative;
display: inline-flex;
align-items: center;
gap: var(--ui-tabs-track-gap);
padding: var(--ui-tabs-track-padding);
border-radius: var(--ui-tabs-track-rounded);
background-color: var(--ui-tabs-track-bg);
/* Scoped so [data-tabs-slot~="indicator"] can resolve --active-tab even when an ancestor
(e.g. [data-navigation-menu-slot~="item"]) sets anchor-scope to its own trigger name. */
anchor-scope: --active-tab;
}
:where(ui-tabs, [data-ui~="tabs"]) [data-tabs-slot~="indicator"] {
position: absolute;
z-index: 0;
pointer-events: none;
border-radius: var(--ui-tabs-indicator-rounded);
background-color: var(--ui-tabs-indicator-bg);
box-shadow: var(--ui-tabs-indicator-shadow);
transition-property: left, right, top, bottom;
transition-duration: var(--default-transition-duration);
transition-timing-function: var(--default-transition-timing-function);
will-change: left, right, top, bottom;
}
:where(ui-tabs, [data-ui~="tabs"]) [data-tabs-slot~="list"] input[type="radio"] {
position: absolute;
clip-path: inset(50%);
opacity: 0;
pointer-events: none;
}
:where(ui-tabs, [data-ui~="tabs"]) [data-tabs-slot~="list"] [data-tabs-slot~="label"] {
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
font-family: var(--ui-tabs-label-font-family);
font-size: var(--ui-tabs-label-font-size);
font-weight: var(--ui-tabs-label-font-weight);
line-height: var(--ui-tabs-label-leading);
min-block-size: var(--ui-tabs-label-min-h);
padding-inline: var(--ui-tabs-label-px);
color: var(--ui-tabs-label-text);
background-color: var(--ui-tabs-label-bg);
text-wrap: balance;
cursor: pointer;
user-select: none;
text-box: trim-both ex alphabetic;
border-radius: var(--ui-tabs-indicator-rounded);
/* ring renders as box-shadow so it composes with the indicator shadow in
the no-anchor fallback below; the ring widths animate from 0 so the
ring grows in smoothly on focus */
--_ring-offset-width: 0px;
--_ring-width: 0px;
--_focus-in: max(0, -1 * sign(var(--_focus-ring-w, 1px)));
--_ring-fill: color-mix(
in oklch,
var(--ui-tabs-label-ring-color) var(--ring-opacity),
transparent
);
box-shadow:
0 0 0 calc(var(--_ring-offset-width) * (1 - var(--_focus-in))) var(--ring-offset-shadow-color),
0 0 0 calc((var(--_ring-offset-width) + var(--_ring-width)) * (1 - var(--_focus-in)))
var(--_ring-fill, var(--ring-shadow-color)),
inset 0 0 0 calc(var(--_ring-offset-width) * var(--_focus-in)) var(--ring-offset-shadow-color),
inset 0 0 0 calc((var(--_ring-offset-width) + var(--_ring-width)) * var(--_focus-in))
var(--_ring-fill, var(--ring-shadow-color));
/* transparent twin of the ring for forced-colors / high-contrast modes */
outline: abs(var(--_focus-ring-w, var(--outline-width))) var(--outline-style) transparent;
outline-offset: calc(
var(--outline-offset) - var(--_focus-in) *
(2 * var(--outline-offset) + abs(var(--_focus-ring-w, var(--outline-width))))
);
transition: var(--default-transition);
}
@media (hover: hover) {
:where(ui-tabs, [data-ui~="tabs"]) [data-tabs-slot~="list"] [data-tabs-slot~="label"]:hover {
color: var(--ui-tabs-label-text--hover);
background-color: var(--ui-tabs-label-bg--hover);
}
}
:where(ui-tabs, [data-ui~="tabs"]) [data-tabs-slot~="list"] [data-tabs-slot~="label"]:active {
color: var(--ui-tabs-label-text--active);
background-color: var(--ui-tabs-label-bg--active);
}
:where(ui-tabs, [data-ui~="tabs"])
[data-tabs-slot~="list"]
input[type="radio"]:checked
+ [data-tabs-slot~="label"] {
color: var(--ui-tabs-label-text--active);
anchor-name: --active-tab;
}
:where(ui-tabs, [data-ui~="tabs"])
[data-tabs-slot~="list"]
input[type="radio"]:focus-visible
+ [data-tabs-slot~="label"] {
--_ring-offset-width: var(--ring-offset-width);
--_ring-width: abs(var(--_focus-ring-w, var(--ring-width)));
outline-color: transparent;
}
/* Indicator positioning: the pill sizes itself from the checked label (exposed
as --active-tab) via anchor() insets. Without anchor positioning the insets
are invalid and the pill collapses, leaving the active tab unmarked (label
background is transparent), so the fallback hides the pill and paints the
checked label directly, losing only the slide animation. */
@supports (anchor-name: --active-tab) {
:where(ui-tabs, [data-ui~="tabs"]) [data-tabs-slot~="indicator"] {
position-anchor: --active-tab;
/* physical top/left/right/bottom: anchor() only accepts physical side keywords */
top: calc(anchor(--active-tab top) + var(--ui-tabs-indicator-inset));
left: calc(anchor(--active-tab left) + var(--ui-tabs-indicator-inset));
right: calc(anchor(--active-tab right) + var(--ui-tabs-indicator-inset));
bottom: calc(anchor(--active-tab bottom) + var(--ui-tabs-indicator-inset));
}
:where(ui-tabs, [data-ui~="tabs"])
[data-tabs-slot~="list"]
input[type="radio"]:checked
+ [data-tabs-slot~="label"] {
background-color: var(--ui-tabs-label-bg--active);
}
}
@supports not (anchor-name: --active-tab) {
:where(ui-tabs, [data-ui~="tabs"]) [data-tabs-slot~="indicator"] {
display: none;
}
:where(ui-tabs, [data-ui~="tabs"])
[data-tabs-slot~="list"]
input[type="radio"]:checked
+ [data-tabs-slot~="label"] {
border-radius: var(--ui-tabs-indicator-rounded);
background-color: var(--ui-tabs-indicator-bg);
box-shadow:
0 0 0 calc(var(--_ring-offset-width) * (1 - var(--_focus-in)))
var(--ring-offset-shadow-color),
0 0 0 calc((var(--_ring-offset-width) + var(--_ring-width)) * (1 - var(--_focus-in)))
var(--_ring-fill, var(--ring-shadow-color)),
inset 0 0 0 calc(var(--_ring-offset-width) * var(--_focus-in))
var(--ring-offset-shadow-color),
inset 0 0 0 calc((var(--_ring-offset-width) + var(--_ring-width)) * var(--_focus-in))
var(--_ring-fill, var(--ring-shadow-color)),
var(--ui-tabs-indicator-shadow);
}
}
/* Nth radio in the list maps to the (N + 1)th child of the root. Selectors
cannot do index arithmetic and sibling-index() is a value function, so the
per-index enumeration (capped at 24 tabs) is needed. Hiding stays
display:none so hidden panels are not focusable. */
:where(ui-tabs, [data-ui~="tabs"]):has(
[data-tabs-slot~="list"] > input[type="radio"]:nth-of-type(1):not(:checked)
)
> [data-tabs-slot~="panel"]:nth-child(2),
:where(ui-tabs, [data-ui~="tabs"]):has(
[data-tabs-slot~="list"] > input[type="radio"]:nth-of-type(2):not(:checked)
)
> [data-tabs-slot~="panel"]:nth-child(3),
:where(ui-tabs, [data-ui~="tabs"]):has(
[data-tabs-slot~="list"] > input[type="radio"]:nth-of-type(3):not(:checked)
)
> [data-tabs-slot~="panel"]:nth-child(4),
:where(ui-tabs, [data-ui~="tabs"]):has(
[data-tabs-slot~="list"] > input[type="radio"]:nth-of-type(4):not(:checked)
)
> [data-tabs-slot~="panel"]:nth-child(5),
:where(ui-tabs, [data-ui~="tabs"]):has(
[data-tabs-slot~="list"] > input[type="radio"]:nth-of-type(5):not(:checked)
)
> [data-tabs-slot~="panel"]:nth-child(6),
:where(ui-tabs, [data-ui~="tabs"]):has(
[data-tabs-slot~="list"] > input[type="radio"]:nth-of-type(6):not(:checked)
)
> [data-tabs-slot~="panel"]:nth-child(7),
:where(ui-tabs, [data-ui~="tabs"]):has(
[data-tabs-slot~="list"] > input[type="radio"]:nth-of-type(7):not(:checked)
)
> [data-tabs-slot~="panel"]:nth-child(8),
:where(ui-tabs, [data-ui~="tabs"]):has(
[data-tabs-slot~="list"] > input[type="radio"]:nth-of-type(8):not(:checked)
)
> [data-tabs-slot~="panel"]:nth-child(9),
:where(ui-tabs, [data-ui~="tabs"]):has(
[data-tabs-slot~="list"] > input[type="radio"]:nth-of-type(9):not(:checked)
)
> [data-tabs-slot~="panel"]:nth-child(10),
:where(ui-tabs, [data-ui~="tabs"]):has(
[data-tabs-slot~="list"] > input[type="radio"]:nth-of-type(10):not(:checked)
)
> [data-tabs-slot~="panel"]:nth-child(11),
:where(ui-tabs, [data-ui~="tabs"]):has(
[data-tabs-slot~="list"] > input[type="radio"]:nth-of-type(11):not(:checked)
)
> [data-tabs-slot~="panel"]:nth-child(12),
:where(ui-tabs, [data-ui~="tabs"]):has(
[data-tabs-slot~="list"] > input[type="radio"]:nth-of-type(12):not(:checked)
)
> [data-tabs-slot~="panel"]:nth-child(13),
:where(ui-tabs, [data-ui~="tabs"]):has(
[data-tabs-slot~="list"] > input[type="radio"]:nth-of-type(13):not(:checked)
)
> [data-tabs-slot~="panel"]:nth-child(14),
:where(ui-tabs, [data-ui~="tabs"]):has(
[data-tabs-slot~="list"] > input[type="radio"]:nth-of-type(14):not(:checked)
)
> [data-tabs-slot~="panel"]:nth-child(15),
:where(ui-tabs, [data-ui~="tabs"]):has(
[data-tabs-slot~="list"] > input[type="radio"]:nth-of-type(15):not(:checked)
)
> [data-tabs-slot~="panel"]:nth-child(16),
:where(ui-tabs, [data-ui~="tabs"]):has(
[data-tabs-slot~="list"] > input[type="radio"]:nth-of-type(16):not(:checked)
)
> [data-tabs-slot~="panel"]:nth-child(17),
:where(ui-tabs, [data-ui~="tabs"]):has(
[data-tabs-slot~="list"] > input[type="radio"]:nth-of-type(17):not(:checked)
)
> [data-tabs-slot~="panel"]:nth-child(18),
:where(ui-tabs, [data-ui~="tabs"]):has(
[data-tabs-slot~="list"] > input[type="radio"]:nth-of-type(18):not(:checked)
)
> [data-tabs-slot~="panel"]:nth-child(19),
:where(ui-tabs, [data-ui~="tabs"]):has(
[data-tabs-slot~="list"] > input[type="radio"]:nth-of-type(19):not(:checked)
)
> [data-tabs-slot~="panel"]:nth-child(20),
:where(ui-tabs, [data-ui~="tabs"]):has(
[data-tabs-slot~="list"] > input[type="radio"]:nth-of-type(20):not(:checked)
)
> [data-tabs-slot~="panel"]:nth-child(21),
:where(ui-tabs, [data-ui~="tabs"]):has(
[data-tabs-slot~="list"] > input[type="radio"]:nth-of-type(21):not(:checked)
)
> [data-tabs-slot~="panel"]:nth-child(22),
:where(ui-tabs, [data-ui~="tabs"]):has(
[data-tabs-slot~="list"] > input[type="radio"]:nth-of-type(22):not(:checked)
)
> [data-tabs-slot~="panel"]:nth-child(23),
:where(ui-tabs, [data-ui~="tabs"]):has(
[data-tabs-slot~="list"] > input[type="radio"]:nth-of-type(23):not(:checked)
)
> [data-tabs-slot~="panel"]:nth-child(24),
:where(ui-tabs, [data-ui~="tabs"]):has(
[data-tabs-slot~="list"] > input[type="radio"]:nth-of-type(24):not(:checked)
)
> [data-tabs-slot~="panel"]:nth-child(25) {
display: none !important;
}
/* ===========================================================================
DIRECTIONAL VARIANTS
=========================================================================== */
:where(ui-tabs, [data-ui~="tabs"])[data-tabs-orientation="vertical"] {
flex-direction: row;
}
:where(ui-tabs, [data-ui~="tabs"])[data-tabs-orientation="vertical"] [data-tabs-slot~="list"] {
flex-direction: column;
align-items: stretch;
min-inline-size: calc(var(--spacing) * 44);
}
:where(ui-tabs, [data-ui~="tabs"])[data-tabs-orientation="vertical"] [data-tabs-slot~="label"] {
text-align: start;
justify-content: start;
}
}// primitives/tabs/tabs.js
"use strict";
/**
* @fileoverview `<ui-tabs>`: HTML web component for keyboard-enhanced tabs.
* @description Light-DOM custom element that augments the CSS-only radio
* tabs pattern with orientation-aware arrow-key navigation.
*
* Keyboard behavior on the focused tab radio:
* - Horizontal (default): ArrowLeft / ArrowRight move between tabs.
* - Vertical (`data-tabs-orientation="vertical"`): ArrowUp / ArrowDown move between tabs.
* - Home / End jump to the first / last enabled tab.
* - Navigation wraps around and skips disabled tabs.
*
* Native radio-group arrow keys already provide a baseline without
* JavaScript; this element makes the keys match the tabs' visual
* orientation and adds Home/End + wrap-around.
*
* @example
* <ui-tabs>
* <div data-tabs-slot="list" role="tablist">
* <input type="radio" role="tab" name="tg" id="tg-1" checked />
* <label for="tg-1" data-tabs-slot="label">One</label>
* <input type="radio" role="tab" name="tg" id="tg-2" />
* <label for="tg-2" data-tabs-slot="label">Two</label>
* </div>
* <div data-tabs-slot="panel">…</div>
* <div data-tabs-slot="panel">…</div>
* </ui-tabs>
*/
import { ZazzElement, defineZazzElement } from "../../base/zazz-element.js";
class UiTabs extends ZazzElement {
setup(signal) {
this.addEventListener("keydown", (event) => this.#onKeydown(event), { signal });
}
/**
* @description Handles arrow-key, Home, and End navigation between tab radios.
*
* @param event - The keydown event.
*/
#onKeydown(event) {
const target = event.target;
if (!(target instanceof HTMLInputElement) || target.type !== "radio")
return;
const list = target.closest('[role="tablist"], [data-tabs-slot~="list"]');
// ignore radios of a nested ui-tabs
if (!list || list.closest('ui-tabs, [data-ui~="tabs"]') !== this)
return;
const tabs = Array.from(list.querySelectorAll('input[type="radio"]'))
.filter((node) => node instanceof HTMLInputElement)
.filter((tab) => !tab.disabled);
if (tabs.length < 2)
return;
const vertical = this.getAttribute("data-tabs-orientation") === "vertical";
const prevKey = vertical ? "ArrowUp" : "ArrowLeft";
const nextKey = vertical ? "ArrowDown" : "ArrowRight";
const index = tabs.indexOf(target);
if (index === -1)
return;
let nextIndex;
switch (event.key) {
case prevKey:
nextIndex = (index - 1 + tabs.length) % tabs.length;
break;
case nextKey:
nextIndex = (index + 1) % tabs.length;
break;
case "Home":
nextIndex = 0;
break;
case "End":
nextIndex = tabs.length - 1;
break;
default:
return;
}
event.preventDefault();
const tab = tabs[nextIndex];
tab.checked = true;
tab.focus();
tab.dispatchEvent(new Event("change", { bubbles: true }));
}
}
defineZazzElement("ui-tabs", UiTabs);
export { UiTabs };Behavior
primitives/tabs/tabs.js
<ui-tabs>: HTML web component for keyboard-enhanced tabs. Light-DOM custom element that augments the CSS-only radio tabs pattern with orientation-aware arrow-key navigation.
Keyboard behavior on the focused tab radio:
- Horizontal (default): ArrowLeft / ArrowRight move between tabs.
- Vertical (
data-tabs-orientation="vertical"): ArrowUp / ArrowDown move between tabs. - Home / End jump to the first / last enabled tab.
- Navigation wraps around and skips disabled tabs.
Native radio-group arrow keys already provide a baseline without JavaScript; this element makes the keys match the tabs' visual orientation and adds Home/End + wrap-around.
Attributes
| Attribute | Kind | Values | Notes |
|---|---|---|---|
data-ui="tabs" | Identity | free value | Identity token in data-ui. |
<ui-tabs> | Tag form | free value | Tag form; styled and, when scripted, registered under this name. |
data-tabs-orientation | Preset | vertical | Preset: a named bundle of values. |
data-tabs-slot | Slot | indicator label label-text list panel | Slot: a part inside the primitive. |
Requires: fields.
Hooks
Set a hook on :root, on a subtree, or inline on one element. A state hook (--…--hover) themes that state only; the primitive keeps its other states.
| Hook | Default | State |
|---|---|---|
--ui-tabs-gap | var(--space-xs) | — |
--ui-tabs-indicator-bg | var(--color-card) | — |
--ui-tabs-indicator-inset | var(--ui-tabs-track-padding) | — |
--ui-tabs-indicator-rounded | var(--ui-field-inset-child-radius) | — |
--ui-tabs-indicator-shadow | var(--shadow-xs) | — |
--ui-tabs-label-bg | transparent | — |
--ui-tabs-label-bg--active | transparent | active |
--ui-tabs-label-bg--hover | transparent | hover |
--ui-tabs-label-font-family | var(--font-family-body) | — |
--ui-tabs-label-font-size | var(--font-size-sm) | — |
--ui-tabs-label-font-weight | var(--font-weight-strong) | — |
--ui-tabs-label-leading | var(--leading-sm) | — |
--ui-tabs-label-min-h | calc(var(--spacing) * 7) | — |
--ui-tabs-label-px | var(--ui-field-px) | — |
--ui-tabs-label-ring-color | var(--ui-field-ring-color) | — |
--ui-tabs-label-text | var(--color-muted-foreground) | — |
--ui-tabs-label-text--active | var(--color-foreground) | active |
--ui-tabs-label-text--hover | var(--color-foreground) | hover |
--ui-tabs-track-bg | var(--color-muted) | — |
--ui-tabs-track-gap | calc(var(--spacing) * 0.5) | — |
--ui-tabs-track-padding | calc(var(--spacing) * 0.5) | — |
--ui-tabs-track-rounded | var(--ui-field-rounded) | — |