Skip to content

Disclosure

Tabs

A tabbed interface with full ARIA roles and keyboard support.

stableEnhancer: enhanceTabsBuilt on <button role="tab">hydrateless/tabs.css

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

Live

Hydrateless ships accessible primitives with near-zero runtime cost.

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.

Live

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 down

Props

The framework bindings expose these props (and forward the rest to the root element).

PropTypeDefaultDescription
valuestringNot setControlled selected tab; pair with `onValueChange` (Vue: `v-model`, Svelte: `bind:value`).
defaultValuestringNot setUncontrolled initial tab. Defaults to the first enabled tab.
onValueChange(value: string) => voidNot setCalled 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 / CallbackPayloadDescription
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 variableUsed for
--hl-surface-2Selected tab background.
--hl-borderTab list underline and outer border.
--hl-focus-ringFocus ring on the active tab.

Accessibility

  • The framework components render role="tablist"/tab/tabpanel, aria-selected, aria-controls, and hidden on 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 (disabled or aria-disabled="true") are skipped during arrow navigation.

Released under the MIT License.