Tabs keep several views of one subject in a single region and let the reader say which one is
showing. Nothing navigates: the panel is swapped in place, so the page around it, its scroll
position and its state stay where they were.

The bar is a real tablist rather than a row of styled links, which is what makes the choice
answerable from a keyboard, and only the visible panel is in the document, so a hidden view
cannot be reached by a tab press or read out by a screen reader.

```tsx demo="navigation/tabs-preview"
```

## Usage

```tsx
import { Tabs } from "@asheeui/web";

<Tabs
  tabs={[
    { id: "summary", label: "Summary", content: <InvoiceSummary invoice={invoice} /> },
    { id: "lines", label: "Lines", badge: 3, content: <InvoiceLines invoice={invoice} /> },
  ]}
/>
```

A tab is an `id`, a `label` and the `content` it reveals, plus three optional parts: an `icon`
before the label, a `badge` after it for a count or a status, and `disabled` for a view that
exists but cannot be opened yet. `name` is read when `label` is absent, which is the older
spelling of the same thing.

## Treatments

`underline` is the default, and it is what a section of a page wants: the bar is the rule under
it and the selected trigger is the part of the rule that is marked. `bordered` puts every
trigger inside one outlined container, which reads as a panel's own switch. `ghost` drops the
container and keeps the triggers, which is what a toolbar beside a heading wants.

```tsx demo="navigation/tabs-treatments"
```

Two different `variant` vocabularies meet on this component. The `variant` prop is the bar's,
and takes `underline`, `bordered` or `ghost`; an individual trigger's treatment is a `Variant`,
the same five `Button` has, and is stated through `options.active` and `options.inactive`.

In `underline` the selected trigger is drawn as an underlined control whatever
`options.active.variant` says. A bar whose rule is underlined and whose selected trigger were a
filled button would be two treatments at once; the colour still applies, so an underlined bar
can still be accented.

## Selection

`defaultActiveId` is the uncontrolled form: it names the tab that starts selected and is read
once, at mount, so changing it afterwards does nothing. Without it the first tab is selected.

`activeId` makes the bar controlled, and `onChange` reports the id the reader asked for. A
controlled bar draws what the prop says rather than what was pressed, so a page that receives
the id and does not store it leaves the bar exactly where it was. That is the contract, not a
failure: the selection is the page's state, and the bar is one of the ways to change it. An
`activeId` that names no tab falls back to the first, so the bar always has one reachable
trigger and one visible panel.

```tsx demo="navigation/tabs-controlled"
```

Both props take whatever a `TabItem.id` takes, a `string` or a `number`. The panel follows
whichever tab the resolved id names, and its contents are remounted on each change, which is why
the panel can animate in without a stale subtree underneath it.

## Props

```props Tabs
```

The defaults read as a cascade rather than a list. `size`, `variant`, `radius` and
`options.active.radius` are registered by the component as `md`, `underline`, `md` and `md`, so
a page with no configuration gets an underlined bar of medium triggers, rounded at `md`.

The trigger states are deliberately not registered. `options.active.variant` and
`options.active.color` resolve from the application's `defaultVariant` and `defaultColor`, so a
product states its selected tab once, and `options.inactive.variant` falls back to `ghost` with
no colour, so an unselected trigger stays quiet whatever the application states. The inactive
radius resolves from the bar's own radius rather than from `defaultRadius`, so rounding the bar
rounds the triggers with it unless they say otherwise.

`components.tabs` is read by both renderers, so one section states the bar for web and native at
once. What each platform does with it is its own: the native bar scrolls when it does not fit,
and its triggers are never shorter than a thumb can reach.

## Accessibility

- The bar is `role="tablist"` with `aria-orientation="horizontal"`. Each trigger is a real
  button with `role="tab"` and `aria-selected`, and the panel is `role="tabpanel"` labelled by its
  own trigger. Only the selected trigger carries `aria-controls`: the panel it names is the only
  one mounted, so an unselected trigger pointing at a panel that is not in the document would
  promise a destination that does not exist.
- The bar is one stop in the page's tab order, not one per trigger: the selected trigger has
  `tabindex="0"` and the rest have `-1`. The arrow keys move to the next and previous trigger
  and wrap at the ends, <kbd>Home</kbd> and <kbd>End</kbd> jump to the first and last, and a
  disabled tab is skipped rather than focused and refused.
- Moving with an arrow key also selects: the panel changes with the focus, which is what a
  tablist calls automatic activation. There is no second press to confirm, so the arrow keys are
  the whole gesture.
- Panels are removed rather than hidden, so the contents of a view that is not showing are
  neither in the accessibility tree nor in the tab order.

## Related

```related sidebar, dropmenu, stepper
```
