--- title: Tabs description: Tabs organize related content into panels where only one is visible at a time, such as settings sections, product details, or dashboards. category: Navigation --- import { meta as TabsMeta, Default, Ghost, Manual } from '@/content/stories/tabs'; import Canvas from '@/components/component-docs/canvas'; import Demo from '@/components/component-docs/demo'; export const config = { dependencies: { components: [Canvas, Demo], scripts: [ '../../components/component-docs/demo.script.tsx', '../../components/component-docs/canvas.script.tsx', '../../components/component-docs/controls.script.tsx', ], }, }; # Tabs

Tabs organize related content into panels where only one is visible at a time, such as settings sections, product details, or dashboards.

## Try it ## Usage Compose `RuiTabList`, `RuiTab`, `RuiTabPanels`, and `RuiTabPanel`. Set `value` to control the active tab. ```tsx import { RuiTabs, RuiTabList, RuiTab, RuiTabPanels, RuiTabPanel } from '@ecopages/radiant-ui/tabs'; Account Security Account settings Security settings ``` ## Custom markup `` coordinates any light-DOM tree that matches its query contract. The `Rui*` helpers stamp these targets; they are not required. ```tsx import '@ecopages/radiant-ui/tabs';
Account settings
; ``` ## Boxed or ghost

`boxed` tabs have a contained background and work well in settings panels. `ghost` tabs sit flush with the page header.

## Manual activation

Set `automatic=\{false}` when switching tabs should require an explicit Enter/Space press.

## Theming Tabs map to **semantic surface + primary roles**, never Tailwind palette steps. Override `--rui-tabs-*` on `rui-tabs`. Variants (`boxed` / `ghost`) stay; they set layout recipes, not a parallel color API. | Part / variant | Default | Override | | --- | --- | --- | | Tab text | `on-background` | theme | | Active indicator | `2px` `--primary` | `--rui-tabs-indicator-width`, `--rui-tabs-indicator-color` | | Tab padding | `--space-control-x` / `--space-control-y` | `--rui-tabs-tab-padding-x`, `--rui-tabs-tab-padding-y` | | Boxed card | `--background` + `--border` + `--rui-tabs-radius` | `--rui-tabs-radius` | | Boxed tab strip | `surface-container-low` | theme | | Panels | `--space-inset` | `--rui-tabs-panel-padding` | | Focus | `focus-ring` at reduced alpha | theme | ```css rui-tabs { --rui-tabs-indicator-color: var(--secondary); --rui-tabs-panel-padding: var(--space-2); } ``` ## Accessibility - Tab list exposes `role="tablist"` with `aria-selected` on the active tab. - Arrow keys move between tabs; activation follows the `automatic` setting. - Each panel is labelled by its corresponding tab via `aria-labelledby`. ## API `RuiTabs` is a custom element (``) coordinating `role="tablist"` / `role="tab"` / `role="tabpanel"` markup authored via the view helpers. ### Attributes (``) | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `variant` | `ghost` · `boxed` | `boxed` | Visual treatment. | | `value` | `string` | `''` | Selected tab id; defaults to the first tab. | | `label` | `string` | `''` | Accessible name for the tab list (prefer `aria-label` on `RuiTabList`). | | `automatic` | `boolean` | `true` | Automatic (focus activates) vs manual (Enter/Space) activation. | ### Light-DOM contract | Target | Required | Host writes | Author owns | | --- | --- | --- | --- | | `[role="tablist"]` | yes | `aria-label` (from `label` when absent) | `aria-label` (preferred) | | `[role="tab"]` | yes | `aria-selected`, `tabIndex` | `data-tab-value` or `id`, `aria-disabled` | | `[role="tabpanel"]` | yes | `hidden`, `tabIndex` when unset | `data-tab-value` or `id` | Do not set `aria-selected`, `tabIndex`, or `hidden` after connect — the host owns those via `resync()`. Nested hosts: none. ### Events | Event | Detail | Description | | --- | --- | --- | | `rui-change` | `{ value: string }` | Emitted when the selected tab changes. | ### Methods | Method | Description | | --- | --- | | `resync()` | Re-applies tablist labeling and selected/hidden state from `value`. | ### View helpers | Component | Target stamped | Notes | | --- | --- | --- | | `RuiTabList` | `[role="tablist"]` | Set `aria-label` here when possible. | | `RuiTab` | `[role="tab"]`, `data-tab-value`, `id="tab-{id}"` | Pairs with `RuiTabPanel` by `id`. | | `RuiTabPanels` | — | Presentation wrapper only. | | `RuiTabPanel` | `[role="tabpanel"]`, `data-tab-value`, `id="panel-{id}"` | Hidden when not selected. | ### CSS classes Public BEM classes (documented via `@cssclass`): | Class | Description | | --- | --- | | `.rui-tabs__list` | `role="tablist"` strip. | | `.rui-tabs__tab` | `role="tab"` button; underline/border per `variant`. | | `.rui-tabs__panels` | Panel group. | | `.rui-tabs__panel` | `role="tabpanel"`; hidden when not selected. | ### Theme roles | Part | CSS variables consumed | | --- | --- | | Active indicator | `--primary` | | Focus ring | `--focus-ring` | | Boxed card / panels | `--background`, `--border`, `--surface-container-low`, `--shadow-sm`, `--radius-container`, `--p-inset` |