PixelTabs

Tabbed panel with roving tabindex and WAI-ARIA keyboard navigation, available as a sugar items[] API or a compositional List/Trigger/Panel API.

  • Sugar API (items[]) for quick setup or compositional API (List/Trigger/Panel) for full control over rendering.
  • Horizontal or vertical orientation with arrow-key navigation, Home/End edges, and roving tabindex.
  • Automatic activation (select on focus) or manual activation (Enter/Space confirms).
  • Optional scrollable tablist with a fade-mask for overflow, and keepMounted for persistent panel state.
  • Pixel and linear surfaces inherit kit-wide tokens; controlled or uncontrolled via value/defaultTab.
Status
stable
Category
navigation
Since
v1.0.0

API

import { PixelTabs } from '@pxlkit/ui-kit';

PixelTabs

PropTypeDefaultDescription
itemsTabItem[]—The tabs as data. Without them, compose PixelTabs.List, PixelTabs.Trigger and PixelTabs.Panel.
defaultValuestring—Uncontrolled initial active tab id. Canonical name; aliases defaultTab.
defaultTabstring—Deprecated. Use defaultValue instead. Retained as alias for one minor.
valuestring—Controlled active tab id. When set, defaultValue is ignored.
onChange(id: string) => void—Fires whenever the active tab changes (controlled or uncontrolled).
surface'pixel' | 'linear'—Visual surface treatment override.
ariaLabelstring'Tabs'Accessible label for the tablist.
orientation'horizontal' | 'vertical''horizontal'Layout direction for the tab list + keyboard nav.
keepMountedbooleanfalseWhen true, all panels render in the DOM (hidden via CSS).
scrollablebooleanfalseWhen true, the tablist scrolls horizontally with a fade-mask. Ignored for vertical orientation.
activationMode'automatic' | 'manual''automatic''automatic' (default) selects on focus. 'manual' requires Enter/Space.
childrenReact.ReactNode—Compositional children. Ignored when items is provided.

ref points to <div>.

PixelTabs.List

PropTypeDefaultDescription
ariaLabelstring'Tabs'Accessible label announced for the tablist.
scrollablebooleanfalseWhen true, the tablist scrolls horizontally with a fade-mask. Ignored for vertical orientation.

Also takes the native attributes and event handlers of <div> (HTMLAttributes<HTMLDivElement>), except role.

ref points to <div>.

PixelTabs.Trigger

PropTypeDefaultDescription
value (required)string—Tab id this trigger controls — must match a PixelTabsPanel value.
iconReact.ReactNode—Optional leading icon node.

Also takes the native attributes and event handlers of <button> (ButtonHTMLAttributes<HTMLButtonElement>), except role.

ref points to <button>.

PixelTabs.Panel

PropTypeDefaultDescription
value (required)string—Tab id this panel belongs to — must match a PixelTabsTrigger value.
keepMountedboolean—Override the root's keepMounted setting for this panel.
borderedbooleanfalseRender with surface-aware border + radius chrome. Defaults to false (no chrome).

Also takes the native attributes and event handlers of <div> (HTMLAttributes<HTMLDivElement>), except role.

ref points to <div>.

Accessibility

WCAG target: 2.1 AA

  • tablist

Renders role="tablist" on the list container with aria-orientation reflecting the orientation prop, role="tab" on each trigger with aria-selected and aria-controls wiring, and role="tabpanel" on the panel with aria-labelledby pointing back at its trigger. Panels are tabIndex=0 so keyboard users can reach panel content after activating a tab. The active panel is the only one rendered by default; keepMounted preserves all panels in the DOM and uses the hidden attribute to mask inactive ones.

Keyboard

KeyActionWhen
TabMoves focus into/out of the tablist; only the active tab is in the tab sequence (roving tabindex).—
ArrowRightFocuses the next tab (wraps to first).orientation="horizontal"
ArrowLeftFocuses the previous tab (wraps to last).orientation="horizontal"
ArrowDownFocuses the next tab (wraps to first).orientation="vertical"
ArrowUpFocuses the previous tab (wraps to last).orientation="vertical"
HomeFocuses the first tab in the list.—
EndFocuses the last tab in the list.—
EnterActivates the focused tab.activationMode="manual"
SpaceActivates the focused tab.activationMode="manual"

Usage

import { PixelTabs } from '@pxlkit/ui-kit';
import type { TabItem } from '@pxlkit/ui-kit';

const ITEMS: TabItem[] = [
  { id: 'overview', label: 'Overview', content: <p>High-level summary of the project.</p> },
  { id: 'activity', label: 'Activity', content: <p>Recent events and commits.</p> },
  { id: 'settings', label: 'Settings', content: <p>Configuration for this workspace.</p> },
];

export function Default() {
  return <PixelTabs items={ITEMS} defaultValue="overview" />;
}

Examples

Default

import { PixelTabs } from '@pxlkit/ui-kit';
import type { TabItem } from '@pxlkit/ui-kit';

const ITEMS: TabItem[] = [
  { id: 'overview', label: 'Overview', content: <p>High-level summary of the project.</p> },
  { id: 'activity', label: 'Activity', content: <p>Recent events and commits.</p> },
  { id: 'settings', label: 'Settings', content: <p>Configuration for this workspace.</p> },
];

export function Default() {
  return <PixelTabs items={ITEMS} defaultValue="overview" />;
}

Controlled

import { useState } from 'react';
import { PixelTabs } from '@pxlkit/ui-kit';
import type { TabItem } from '@pxlkit/ui-kit';

const ITEMS: TabItem[] = [
  { id: 'overview', label: 'Overview', content: <p>High-level summary of the project.</p> },
  { id: 'activity', label: 'Activity', content: <p>Recent events and commits.</p> },
  { id: 'settings', label: 'Settings', content: <p>Configuration for this workspace.</p> },
];

export function Controlled() {
  const [active, setActive] = useState('activity');
  return (
    <div className="space-y-2">
      <PixelTabs items={ITEMS} value={active} onChange={setActive} />
      <p className="text-xs text-retro-muted">Active tab: {active}</p>
    </div>
  );
}

Vertical

import { PixelTabs } from '@pxlkit/ui-kit';
import type { TabItem } from '@pxlkit/ui-kit';

const ITEMS: TabItem[] = [
  { id: 'overview', label: 'Overview', content: <p>High-level summary of the project.</p> },
  { id: 'activity', label: 'Activity', content: <p>Recent events and commits.</p> },
  { id: 'settings', label: 'Settings', content: <p>Configuration for this workspace.</p> },
];

export function Vertical() {
  return <PixelTabs items={ITEMS} defaultValue="overview" orientation="vertical" />;
}

Manual activation

import { PixelTabs } from '@pxlkit/ui-kit';
import type { TabItem } from '@pxlkit/ui-kit';

const ITEMS: TabItem[] = [
  { id: 'overview', label: 'Overview', content: <p>High-level summary of the project.</p> },
  { id: 'activity', label: 'Activity', content: <p>Recent events and commits.</p> },
  { id: 'settings', label: 'Settings', content: <p>Configuration for this workspace.</p> },
];

export function ManualActivation() {
  return (
    <PixelTabs
      items={ITEMS}
      defaultValue="overview"
      activationMode="manual"
      ariaLabel="Manual activation tabs"
    />
  );
}

Surfaces

import { PixelTabs } from '@pxlkit/ui-kit';
import type { TabItem } from '@pxlkit/ui-kit';

const ITEMS: TabItem[] = [
  { id: 'overview', label: 'Overview', content: <p>High-level summary of the project.</p> },
  { id: 'activity', label: 'Activity', content: <p>Recent events and commits.</p> },
  { id: 'settings', label: 'Settings', content: <p>Configuration for this workspace.</p> },
];

export function Surfaces() {
  return (
    <div className="grid grid-cols-1 gap-6">
      <PixelTabs items={ITEMS} defaultValue="overview" surface="pixel" ariaLabel="Pixel tabs" />
      <PixelTabs items={ITEMS} defaultValue="overview" surface="linear" ariaLabel="Linear tabs" />
    </div>
  );
}

Scrollable

import { PixelTabs } from '@pxlkit/ui-kit';
import type { TabItem } from '@pxlkit/ui-kit';

const MANY: TabItem[] = Array.from({ length: 9 }, (_, i) => ({
  id: `tab-${i + 1}`,
  label: `Section ${i + 1}`,
  content: <p>Contents of section {i + 1}.</p>,
}));

export function Scrollable() {
  return (
    <div className="max-w-sm">
      <PixelTabs items={MANY} defaultValue="tab-1" scrollable ariaLabel="Scrollable tabs" />
    </div>
  );
}

Keep mounted

import { PixelTabs } from '@pxlkit/ui-kit';
import type { TabItem } from '@pxlkit/ui-kit';

const ITEMS: TabItem[] = [
  { id: 'overview', label: 'Overview', content: <p>High-level summary of the project.</p> },
  { id: 'activity', label: 'Activity', content: <p>Recent events and commits.</p> },
  { id: 'settings', label: 'Settings', content: <p>Configuration for this workspace.</p> },
];

export function KeepMounted() {
  return (
    <PixelTabs
      items={ITEMS}
      defaultValue="overview"
      keepMounted
      ariaLabel="Persistent panels"
    />
  );
}

Compositional

import { PixelTabs } from '@pxlkit/ui-kit';

export function Compositional() {
  return (
    <PixelTabs defaultValue="one">
      <PixelTabs.List ariaLabel="Compositional tabs">
        <PixelTabs.Trigger value="one">One</PixelTabs.Trigger>
        <PixelTabs.Trigger value="two">Two</PixelTabs.Trigger>
        <PixelTabs.Trigger value="three">Three</PixelTabs.Trigger>
      </PixelTabs.List>
      <PixelTabs.Panel value="one">First panel.</PixelTabs.Panel>
      <PixelTabs.Panel value="two">Second panel.</PixelTabs.Panel>
      <PixelTabs.Panel value="three">Third panel.</PixelTabs.Panel>
    </PixelTabs>
  );
}

Related