---
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.
---

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

<p class="docs-lede">Tabs organize related content into panels where only one is visible at a time, such as settings sections, product details, or dashboards.</p>

## Try it

<Demo of={Default} meta={TabsMeta} />

## 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';

<RuiTabs value="account" variant="boxed">
  <RuiTabList>
    <RuiTab id="account">Account</RuiTab>
    <RuiTab id="security">Security</RuiTab>
  </RuiTabList>
  <RuiTabPanels>
    <RuiTabPanel id="account">Account settings</RuiTabPanel>
    <RuiTabPanel id="security">Security settings</RuiTabPanel>
  </RuiTabPanels>
</RuiTabs>
```

## Custom markup

`<rui-tabs>` 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';

<rui-tabs value="account">
  <div role="tablist" aria-label="Settings">
    <button type="button" role="tab" id="tab-account" data-tab-value="account" aria-controls="panel-account">
      Account
    </button>
    <button type="button" role="tab" id="tab-security" data-tab-value="security" aria-controls="panel-security">
      Security
    </button>
  </div>
  <div role="tabpanel" id="panel-account" data-tab-value="account" aria-labelledby="tab-account">
    Account settings
  </div>
  <div role="tabpanel" id="panel-security" data-tab-value="security" aria-labelledby="tab-security" hidden>
    Security settings
  </div>
</rui-tabs>;
```

## Boxed or ghost

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

<Canvas of={Ghost} meta={TabsMeta} />

## Manual activation

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

<Canvas of={Manual} meta={TabsMeta} />

## 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 the `<rui-tabs>` custom-element class and `RuiTabsElement` is an alias for the same class. It coordinates `role="tablist"` / `role="tab"` / `role="tabpanel"` markup authored via the retained `RuiTabList`, `RuiTab`, `RuiTabPanels`, and `RuiTabPanel` helpers.

### Attributes (`<rui-tabs>`)

| 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` |
