Tabs

A hand-drawn tabbed interface following the WAI-ARIA Tabs pattern.

Overview

Tabs let people switch between related panels of content in the same space. One tab is active at a time; its panel is shown while the others are hidden.

Anatomy

A role="tablist" of role="tab" buttons above role="tabpanel" regions. The active tab is filled with a sketchy box (@ghds/sketch-core) and shown in the emphasised text colour; its panel is associated via aria-controls/aria-labelledby. The API is data-driven — pass an items array of { value, label, content }.

Variants & States

State Status Notes
Selected Implemented Active tab: filled box + emphasised label + aria-selected.
Disabled Implemented Per-item; skipped by keyboard navigation.
Focus Implemented Roving tabindex — focus lives on the active tab.
Hover Implemented

Usage

Do

  • Keep tab labels short and parallel in phrasing.
  • Use tabs for alternate views of peer content, not for sequential steps.
  • Keep it controlled if the active tab must stay in sync with other state (value + onValueChange).

Don't

  • Don't use tabs when all content should be visible at once — use sections instead.
  • Don't nest tabs within tabs; it gets confusing quickly.

Accessibility

Key Action
Arrow keys Move to and activate the previous/next tab (wraps).
Home / End Activate the first / last tab.
Tab Move focus out of the tablist to the active panel.

Content

A short label per tab and the panel content. Panel content is a ReactNode on React/React Native; on Web Components it is panel text (content string) — for rich markup, render your own panel and drive it from the value-change event.

Props API

React@ghds/react

PropTypeDefaultDescription
itemsTabItem[]{ value, label, content, disabled? } per tab.
valuestringControlled active value.
defaultValuestringInitial value when uncontrolled (defaults to first).
onValueChange(value: string) => voidFires when the active tab changes.
labelstringAccessible name for the tablist.

Web Components@ghds/web-components

PropTypeDefaultDescription
itemsGhTabItem[]{ value, label, content?: string, disabled? } (property).
valuestringControlled active value.
labelstringAccessible name for the tablist.
value-change eventCustomEvent<string>Dispatched with the new value.

React Native@ghds/react-native

PropTypeDefaultDescription
itemsTabItem[]{ value, label, content, disabled? } per tab.
valuestringControlled active value.
defaultValuestringInitial value when uncontrolled.
onValueChange(value: string) => voidFires when the active tab changes.
labelstringAccessible name.
testIDstringTest handle for queries.

Code examples

// React — @ghds/react
import { Tabs } from '@ghds/react/tabs';

<Tabs
  label="Product details"
  items={[
    { value: 'overview', label: 'Overview', content: <p>The overview panel.</p> },
    { value: 'specs', label: 'Specs', content: <p>The specifications panel.</p> },
  ]}
/>;
<!-- Web Components — @ghds/web-components -->
<script type="module">
  import '@ghds/web-components/tabs';
</script>
<gh-tabs id="my-tabs" label="Product details"></gh-tabs>
<script>
  const el = document.getElementById('my-tabs');
  el.items = [
    { value: 'overview', label: 'Overview', content: 'The overview panel.' },
    { value: 'specs', label: 'Specs', content: 'The specifications panel.' },
  ];
  el.addEventListener('value-change', (e) => { el.value = e.detail; });
</script>
// React Native — @ghds/react-native
import { Tabs } from '@ghds/react-native/tabs';
import { Text } from '@ghds/react-native/theme';

<Tabs
  label="Product details"
  items={[
    { value: 'overview', label: 'Overview', content: <Text>Overview</Text> },
    { value: 'specs', label: 'Specs', content: <Text>Specs</Text> },
  ]}
/>;

Live Demo

React @ghds/react

Web Components @ghds/web-components