Disclosure
Tabs
A tabbed interface with full ARIA roles and keyboard support.
A tabbed interface following the WAI-ARIA tabs pattern. Two markups are supported: role-based tabs (role="tablist", role="tab", role="tabpanel") that render the selected state on the server, and a CSS-only baseline built on visually hidden radios that switches panels with no JavaScript. The enhancer adds the roving tabindex, arrow-key navigation, manual or automatic activation, and keeps aria-selected and hidden in sync.
Tabs
Render aria-selected, tabindex, and hidden on the server so the selected tab shows before any JavaScript runs. Left/Right move focus, Home/End jump to the ends, and Enter/Space activate (manual activation is the default).
Hydrateless ships accessible primitives with near-zero runtime cost.
CSS-first components, optional enhancers, auto-init, and design tokens.
Free and open source under the MIT license.
CSS-only baseline
Each tab is a <label class="hl-tab"> wrapping a radio, and :has(input:checked) reveals the matching panel. Panels switch with no JavaScript; the enhancer upgrades the same markup to the full ARIA pattern when it runs.
Overview panel, shown by the checked radio.
Features panel, revealed by CSS alone.
JavaScript
This component ships an optional enhancer. With auto-init it loads automatically; to wire it yourself, import it directly:
import { enhanceTabs } from '@hydrateless/enhancers/tabs';
const handle = enhanceTabs(document);
// handle.api -> imperative controls
// handle.destroy() -> tear everything downProps
The framework bindings expose these props (and forward the rest to the root element).
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | Not set | Controlled selected tab; pair with `onValueChange` (Vue: `v-model`, Svelte: `bind:value`). |
defaultValue | string | Not set | Uncontrolled initial tab. Defaults to the first enabled tab. |
onValueChange | (value: string) => void | Not set | Called with the new tab value after every selection change. |
activation | 'manual' | 'automatic' | 'manual' | Whether arrow keys only move focus (`manual`, activate with Enter/Space) or select immediately (`automatic`). |
orientation | 'horizontal' | 'vertical' | 'horizontal' | Tab list direction; vertical lists use Up/Down instead of Left/Right. |
Events and callbacks
| Event / Callback | Payload | Description |
|---|---|---|
hl:change | { value: string } | Fires when the selected tab changes (also the `onValueChange` callback). |
CSS variables
Override these on any ancestor to theme the component. See the theme studio to preview changes live.
| CSS variable | Used for |
|---|---|
--hl-surface-2 | Selected tab background. |
--hl-border | Tab list underline and outer border. |
--hl-focus-ring | Focus ring on the active tab. |
Accessibility
- The framework components render
role="tablist"/tab/tabpanel,aria-selected,aria-controls, andhiddenon the server, so there is no flash of unselected state before hydration. - A roving tabindex keeps the tab list a single tab stop; each panel has
tabindex="0"so keyboard users can reach its content. - Disabled tabs (
disabledoraria-disabled="true") are skipped during arrow navigation.