Tabs
A row of tabs that switches views, with their TabsPanels or with the view the app renders.
import { Tabs, type TabsItem } from "@etched/reticle/tabs";
const sections: TabsItem[] = [
{ value: "overview", label: "Overview" },
{ value: "runs", label: "Runs" },
{ value: "owners", label: "Owners" },
];
export function Preview() {
return <Tabs aria-label="Chip sections" tabs={sections} />;
}Installation
import { Tabs, TabsNav, TabsPanel } from "@etched/reticle/tabs";Examples
Segmented and underline
import { Tabs, type TabsItem } from "@etched/reticle/tabs";
const sections: TabsItem[] = [
{ value: "overview", label: "Overview" },
{ value: "runs", label: "Runs" },
{ value: "owners", label: "Owners" },
];
export function SegmentedAndUnderline() {
return (
<div className="grid">
<Tabs aria-label="Chip sections" tabs={sections} />
<Tabs aria-label="Chip sections, underline" variant="underline" tabs={sections} selectedValue="runs" />
</div>
);
}Small
import { Tabs, type TabsItem } from "@etched/reticle/tabs";
const sections: TabsItem[] = [
{ value: "overview", label: "Overview" },
{ value: "runs", label: "Runs" },
{ value: "owners", label: "Owners" },
];
export function Small() {
return (
<div className="grid">
<Tabs aria-label="Chip sections, small" size="sm" tabs={sections} />
<Tabs aria-label="Chip sections, small underline" size="sm" variant="underline" tabs={sections} />
</div>
);
}Controlled view switch
Showing the pipeline view.
import { useState } from "react";
import { Tabs, type TabsItem } from "@etched/reticle/tabs";
const views: TabsItem[] = [
{ value: "pipeline", label: "Pipeline" },
{ value: "summary", label: "Summary" },
];
export function ControlledViewSwitch() {
const [view, setView] = useState("pipeline");
return (
<div className="grid">
<Tabs aria-label="Role views" tabs={views} value={view} onValueChange={setView} />
<p>Showing the {view} view.</p>
</div>
);
}A disabled tab
import { Tabs, type TabsItem } from "@etched/reticle/tabs";
const settings: TabsItem[] = [
{ value: "users", label: "Users" },
{ value: "notifications", label: "Notifications" },
{ value: "data", label: "Data quality", disabled: true },
];
export function ADisabledTab() {
return <Tabs aria-label="Settings" tabs={settings} />;
}Overflow
Tabs that do not fit scroll sideways.
import { Tabs, type TabsItem } from "@etched/reticle/tabs";
const views: TabsItem[] = [
{ value: "pipeline", label: "Pipeline" },
{ value: "summary", label: "Summary" },
];
const many: TabsItem[] = ["Overview", "Runs", "Thermal", "Power", "Logs", "Owners", "History", "Alerts", "Settings"].map((label) => ({
value: label.toLowerCase(),
label,
}));
export function Overflow() {
return (
<div className="grid">
<Tabs aria-label="Chip views" tabs={many} />
<Tabs aria-label="Chip views, underline" variant="underline" tabs={many} />
</div>
);
}Panels
TabsPanels inside Tabs are linked to their tabs and stay mounted while hidden: type a note, switch tabs, and come back.
Oct 14 · Moved to Interviewing by Sam Lee
import { InputArea } from "@etched/reticle/input";
import { Tabs, TabsPanel } from "@etched/reticle/tabs";
export function Panels() {
return (
<Tabs aria-label="Candidate workspace" variant="underline" tabs={[{ value: "notes", label: "Notes" }, { value: "activity", label: "Activity" }]}>
<TabsPanel value="notes" className="grid">
<InputArea aria-label="Note" placeholder="Type a note, then switch tabs" rows={2} />
</TabsPanel>
<TabsPanel value="activity">
<p>Oct 14 · Moved to Interviewing by Sam Lee</p>
</TabsPanel>
</Tabs>
);
}Links between pages
TabsNav is a nav of links with aria-current="page", for tabs that are URLs. Tab moves between the links.
import { TabsNav, type TabsItem } from "@etched/reticle/tabs";
const sections: TabsItem[] = [
{ value: "overview", label: "Overview" },
{ value: "runs", label: "Runs" },
{ value: "owners", label: "Owners" },
];
export function LinksBetweenPages() {
return (
<div className="grid">
<TabsNav
aria-label="Settings sections"
variant="underline"
links={[
{ href: "#tabs", label: "Users", current: true },
{ href: "#tabs-notifications", label: "Notifications" },
{ href: "#tabs-data", label: "Data quality" },
]}
/>
<TabsNav
aria-label="Leaderboard period"
size="sm"
links={[
{ href: "#tabs-week", label: "Week" },
{ href: "#tabs-month", label: "Month", current: true },
{ href: "#tabs-quarter", label: "Quarter" },
]}
/>
</div>
);
}API reference
TabsPanel
| Prop | Type | Default | Description |
|---|---|---|---|
| value * | string | – | The value of its tab. |
| children * | ReactNode | – | |
| className | string | – |
Tabs
| Prop | Type | Default | Description |
|---|---|---|---|
| tabs * | TabsItem[] | – | |
| value | string | – | Selected tab, controlled. |
| selectedValue | string | the first tab | First selected tab, uncontrolled. |
| onValueChange | (value: string) => void | – | |
| activateOnFocus | boolean | false | Select a tab when the arrow keys move focus to it. Otherwise Enter or Space selects it. |
| variant | "segmented" | "underline" | "segmented" | |
| size | "base" | "sm" | "base" | |
| aria-label | string | – | Names the tab list, for example "Role views". |
| children | ReactNode | – | TabsPanels, one per tab. Without them, the app renders the selected view itself. |
| className | string | – | Class on the root, for layout such as width. |