---
outline: deep
---

# Tabs

Tabs organize content into panels, showing one at a time. Progressive enhancement — plain HTML is enhanced with ARIA roles, keyboard navigation, and an animated indicator.

**`<l-tabs>`** — Progressive Custom Element

## Options

### Enclosed variant

Add `variant="enclosed"` for a pill-shaped tablist with a sliding background indicator.

```html
<l-tabs variant="enclosed">
  <div>
    <button name="account">Account</button>
    <button name="password">Password</button>
    <button name="notifications">Notifications</button>
  </div>
  <div class="p-4 text-sm text-secondary">Make changes to your account here.</div>
  <div class="p-4 text-sm text-secondary">Change your password here.</div>
  <div class="p-4 text-sm text-secondary">Manage your notification preferences.</div>
</l-tabs>
```

### Line variant

Add `variant="line"` for a tablist with a sliding underline indicator.

```html
<l-tabs variant="line">
  <div>
    <button name="account">Account</button>
    <button name="password">Password</button>
    <button name="notifications">Notifications</button>
  </div>
  <div class="p-4 text-sm text-secondary">Make changes to your account here.</div>
  <div class="p-4 text-sm text-secondary">Change your password here.</div>
  <div class="p-4 text-sm text-secondary">Manage your notification preferences.</div>
</l-tabs>
```

Restyle the active underline and the static bottom border with `--indicator-color`, `--indicator-thickness`, `--track-color` and `--track-thickness`.

```html
<l-tabs
  variant="line"
  style="
    --indicator-color: var(--l-color-bg-fill-info-strong);
    --indicator-thickness: 3px;
    --track-color: var(--l-color-divider);
  "
>
  <div>
    <button name="overview">Overview</button>
    <button name="analytics">Analytics</button>
    <button name="reports">Reports</button>
  </div>
  <div class="p-4 text-sm text-secondary">High-level overview.</div>
  <div class="p-4 text-sm text-secondary">Traffic and engagement analytics.</div>
  <div class="p-4 text-sm text-secondary">Exported reports.</div>
</l-tabs>
```

### Full width

Add `full-width` to stretch tabs across the container.

```html
<l-tabs
  variant="enclosed"
  full-width
>
  <div>
    <button name="account">Account</button>
    <button name="password">Password</button>
    <button name="notifications">Notifications</button>
  </div>
  <div class="p-4 text-sm text-secondary">Make changes to your account here.</div>
  <div class="p-4 text-sm text-secondary">Change your password here.</div>
  <div class="p-4 text-sm text-secondary">Manage your notification preferences.</div>
</l-tabs>
```

### Default active tab

Set `value="1"` to activate a specific tab on load (0-based index).

```html
<l-tabs
  variant="enclosed"
  value="1"
>
  <div>
    <button name="account">Account</button>
    <button name="password">Password</button>
    <button name="notifications">Notifications</button>
  </div>
  <div class="p-4 text-sm text-secondary">Make changes to your account here.</div>
  <div class="p-4 text-sm text-secondary">Change your password here.</div>
  <div class="p-4 text-sm text-secondary">Manage your notification preferences.</div>
</l-tabs>
```

## Accessibility

### Criteria

- **Role** — First child div gets `role="tablist"`, buttons get `role="tab"`, remaining divs get `role="tabpanel"`
- **Linked controls** — Each tab has `aria-controls` pointing to its panel; each panel has `aria-labelledby` pointing back to its tab
- **Selection state** — Active tab has `aria-selected="true"`; inactive tabs have `aria-selected="false"`
- **Focus management** — Roving tabindex — active tab has `tabindex="0"`, others `tabindex="-1"`. Panels have `tabindex="0"` for keyboard access
- **Hidden content** — Inactive panels use the `hidden` attribute
- **Motion** — Indicator animation respects `prefers-reduced-motion`

### Rules

- The first child of `l-tabs` must be a `div` containing `button` elements for the tab triggers
- Each remaining child `div` maps to a tab panel in order
- Do not add `role` or `aria-*` attributes manually — the custom element sets them automatically

### Keyboard interactions

- `ArrowRight` — Moves focus to the next tab and activates it (horizontal orientation)
- `ArrowLeft` — Moves focus to the previous tab and activates it (horizontal orientation)
- `ArrowDown` — Moves focus to the next tab and activates it (vertical orientation)
- `ArrowUp` — Moves focus to the previous tab and activates it (vertical orientation)
- `Home` — Moves focus to the first tab and activates it
- `End` — Moves focus to the last tab and activates it
- `Tab` — Moves focus out of the tablist to the active panel

## API reference

### Importing

```js
import 'luxen-ui/tabs';
```

```css
@import 'luxen-ui/css/tabs/enclosed';
```

```css
@import 'luxen-ui/css/tabs/line';
```

### Attributes & Properties

- **variant**: `TabsVariant` (default: `'line'`) — Visual variant.
- **value**: `string` (default: `'0'`) — Index of the active tab (0-based).
- **full-width**: `boolean` (default: `false`) — Stretch tabs to fill container width.
- **orientation**: `TabsOrientation` (default: `'horizontal'`) — Tab orientation.

### Events

- **change** — Fired when the active tab changes. Bubbles. Properties: `index: number`, `name: string | null`.

### CSS custom properties

- `--indicator-color` (default: `var(--l-color-text-primary)`) — `line` variant: color of the active underline that slides under the selected tab.
- `--indicator-thickness` (default: `2px`) — `line` variant: thickness of the active underline.
- `--track-color` (default: `var(--l-color-border)`) — `line` variant: color of the static bottom border the tabs sit on.
- `--track-thickness` (default: `1px`) — `line` variant: thickness of the static bottom border.
