---
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';
AccountSecurityAccount settingsSecurity 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
Security 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` |