Tabs
Tabs switch between panels of related content in the same place. Only the selected panel is rendered, and the tab bar has room for actions on the right.
import { Tabs } from 'jarvis-react-ui';
Basic
Pass the tabs as items, each with a unique value, a label and its panel content. Without
value the component manages the selection itself and starts with defaultValue, or the first
enabled tab.
import { CodeBlock, Tabs } from 'jarvis-react-ui';
export default function Basic() {
return (
<Tabs
aria-label="Package manager"
items={[
{
value: 'npm',
label: 'npm',
content: <CodeBlock code="npm install jarvis-react-ui" />,
},
{
value: 'pnpm',
label: 'pnpm',
content: <CodeBlock code="pnpm add jarvis-react-ui" />,
},
{
value: 'yarn',
label: 'yarn',
content: <CodeBlock code="yarn add jarvis-react-ui" />,
},
]}
/>
);
}
Controlled
Pass value and onValueChange to own the selection, e.g. to sync it with the URL or to change
it from elsewhere. onValueChange is also called in uncontrolled mode.
import { useState } from 'react';
import { Button, Tabs } from 'jarvis-react-ui';
export default function Controlled() {
const [tab, setTab] = useState('status');
return (
<div className="flex w-full flex-col gap-3">
<Tabs
aria-label="Reactor"
value={tab}
onValueChange={setTab}
panelClassName="text-[11px] text-text-secondary"
items={[
{ value: 'status', label: 'Status', content: 'Output stable at 72 percent.' },
{ value: 'history', label: 'History', content: 'No incidents in 30 days.' },
{ value: 'config', label: 'Config', content: 'Auto-throttle enabled.' },
]}
/>
<div className="flex items-center gap-2">
<Button size="sm" onClick={() => setTab('status')}>
Reset
</Button>
<span className="font-mono text-[10px] text-text-muted">value: {tab}</span>
</div>
</div>
);
}
Disabled tab
A tab with disabled: true is dimmed, cannot be clicked and is skipped by the arrow keys.
import { Tabs } from 'jarvis-react-ui';
export default function DisabledTab() {
return (
<Tabs
aria-label="Sensors"
panelClassName="text-[11px] text-text-secondary"
items={[
{ value: 'radar', label: 'Radar', content: '3 contacts in range.' },
{ value: 'lidar', label: 'Lidar', content: 'Calibrating.', disabled: true },
{ value: 'thermal', label: 'Thermal', content: 'No heat signatures.' },
]}
/>
);
}
Actions
actions renders content at the right end of the tab bar, such as a small
IconButton. Actions belong to the whole tab set, not to a single tab.
import { useState } from 'react';
import { RefreshCw } from 'lucide-react';
import { IconButton, Tabs } from 'jarvis-react-ui';
export default function WithActions() {
const [refreshed, setRefreshed] = useState(0);
return (
<Tabs
aria-label="Logs"
panelClassName="text-[11px] text-text-secondary"
actions={
<IconButton
icon={RefreshCw}
label="Refresh logs"
size="sm"
onClick={() => setRefreshed((n) => n + 1)}
/>
}
items={[
{ value: 'all', label: 'All', content: `128 entries, refreshed ${refreshed}x.` },
{
value: 'errors',
label: 'Errors',
content: `2 entries, refreshed ${refreshed}x.`,
},
]}
/>
);
}
Use panelClassName to style the panel container, e.g. its typography or padding.
Keyboard
| Key | Action |
|---|---|
Tab |
Moves focus into the tab list (to the selected tab) and on to the panel content. |
ArrowRight / ArrowLeft |
Selects the next / previous enabled tab, wrapping around. |
Home / End |
Selects the first / last enabled tab. |
Selection follows focus: moving with the arrow keys selects the tab immediately.
Sound
Hovering a tab plays hover_button and selecting a different tab plays click through the
nearest SfxProvider. Without a provider the tabs stay silent. See
Sound effects.
Accessibility
- Follows the WAI-ARIA tabs pattern: a
role="tablist"ofrole="tab"buttons and onerole="tabpanel". - Name the tab list with
aria-label. - Each tab has
aria-selectedandaria-controlspointing at the panel; the panel is labelled by its tab througharia-labelledby. - Roving tabindex: only the selected tab is in the tab order (
tabIndex={0}), the others havetabIndex={-1}and are reached with the arrow keys. - Disabled tabs use the native
disabledattribute.
API
| Prop | Type | Default | Description |
|---|---|---|---|
items (required) |
TabItem[] |
Tabs in display order. | |
actions |
ReactNode |
Content rendered at the right end of the tab bar, e.g. an IconButton. |
|
aria-label |
string |
Accessible name of the tab list. | |
className |
string |
Additional class names for the root element (wraps the tab bar and the panel). | |
defaultValue |
string |
the first enabled tab |
Initially selected value when uncontrolled. |
onValueChange |
((value: string) => void) |
Called with the new value when the user selects a tab. | |
panelClassName |
string |
Additional class names for the role="tabpanel" container. |
|
value |
string |
Controlled selected value. Leave undefined to use defaultValue. |
Type TabItem
A single tab of a Tabs component.
| Field | Type | Default | Description |
|---|---|---|---|
content (required) |
ReactNode |
Panel content shown while the tab is selected. | |
label (required) |
ReactNode |
Text of the tab button. | |
value (required) |
string |
Unique value identifying the tab. | |
disabled |
boolean |
false |
Disables the tab. |