Tabs
Switch a view in place, or move between pages.
Overview
Tabs switch a panel on the same page. A row of links to other pages is NavTabs.
Switch view
import { useState } from "react";
import { Body, Stack, Tabs } from "@hackcove/ui";
function Example() {
const [value, setValue] = useState("overview");
return (
<Stack>
<Tabs
label="Project views"
value={value}
onChange={setValue}
items={[
{ value: "overview", label: "Overview", controls: "panel" },
{ value: "activity", label: "Activity", controls: "panel" },
{ value: "settings", label: "Settings", controls: "panel" },
]}
/>
<Body id="panel" role="tabpanel">
Showing {value}.
</Body>
</Stack>
);
}Tabs switches a view in place; the panel it controls sits right under the row.Switch page
import { NavTabs } from "@hackcove/ui";
<NavTabs
label="Sections"
items={[
{ label: "Quotes", href: "#quotes", active: true },
{ label: "Invoices", href: "#invoices" },
]}
/>NavTabs are links, so the URL and history move.import { useState } from "react";
import { Tabs } from "@hackcove/ui";
function Example() {
const [value, setValue] = useState("quotes");
return (
<Tabs
label="Sections"
value={value}
onChange={(next) => {
setValue(next);
location.assign(`#${next}`);
}}
items={[
{ value: "quotes", label: "Quotes" },
{ value: "invoices", label: "Invoices" },
]}
/>
);
}Tabs row that only navigates leaves the URL, the back button and a new tab behind.Count
import { useState } from "react";
import { Tabs } from "@hackcove/ui";
function Example() {
const [value, setValue] = useState("shots");
return (
<Tabs
label="Assets"
value={value}
onChange={setValue}
items={[
{ value: "shots", label: "Shots", count: 12 },
{ value: "clips", label: "Clips", count: 3 },
]}
/>
);
}count sits muted after the label and is read as part of the tab's name.import { useState } from "react";
import { Tabs } from "@hackcove/ui";
function Example() {
const [value, setValue] = useState("shots");
return (
<Tabs
label="Assets"
value={value}
onChange={setValue}
items={[
{ value: "shots", label: "Shots (12)" },
{ value: "clips", label: "Clips (3)" },
]}
/>
);
}Rail
import { NavTabs } from "@hackcove/ui";
<NavTabs
orientation="vertical"
label="Sections"
items={[
{ label: "Overview", href: "#overview" },
{ label: "Button", href: "#button", active: true },
{ label: "Card", href: "#card" },
]}
/>NavTabs with orientation="vertical": one nav landmark, the current page marked.import { ButtonLink, Stack } from "@hackcove/ui";
<Stack gap="xs">
<ButtonLink href="#overview" variant="ghost">
Overview
</ButtonLink>
<ButtonLink href="#button" variant="ghost">
Button
</ButtonLink>
<ButtonLink href="#card" variant="ghost">
Card
</ButtonLink>
</Stack>ButtonLinks is not a nav landmark, and nothing marks the current page.Usage
Tabsswitches a view in place; the caller ownsvalueandonChange, and the panel sits right under the row.- Going to another page is
NavTabs: links, so the URL, the back button and a new tab all work. - A
countsits muted after the label and is read as part of the tab's name. variant="pill"fills the current tab, for under aPageHeader.- A disabled tab keeps a
titleas the reason. NavTabswithorientation="vertical"is the rail of aSidebarLayout: one link per line.
Accessibility
- The row is a tab list named by
label; the arrow keys, Home and End move between the tabs, and a disabled tab is skipped. - Give the panel
role="tabpanel"and a tabcontrolsthat points at itsid. - A tab is a
buttonwithrole="tab"andaria-selected; only the current tab is in the tab order, and the arrow keys move and choose. NavTabsis a<nav>named bylabel; the current link getsaria-current="page".
API
Tabs
Underline tabs for switching a view in place: a row over a bottom rule, the current tab underlined in the accent. items are { value, label } buttons (value and onChange are the caller's state; the arrow keys, Home and End move between tabs) or links (href, or render to draw the app's own router <Link>; value marks the current one, which gets aria-current). variant="pill" fills the current tab instead, with no rule, for under a PageHeader. An item may carry a count, and a button one disabled (with a title for the reason); disabled on the row disables every tab. label names the tab list. The panel is the caller's: give it role="tabpanel", and a tab controls to point at it.
disabledEvery option is disabled, e.g. until the page has hydrated.
- Type
boolean
items required- Type
SwitchButton[] | SwitchLink[]
label requiredThe accessible name of the group.
- Type
string
onChangeCalled with the option's value when one is chosen, by click or arrow key.
- Type
((value: string) => void)
value requiredThe value of the current option.
- Type
string
variantunderline (default) is a row over a bottom rule; pill fills the current tab, as NavTabs does.
- Type
"underline" | "pill"- Default
underline
NavTabs
A row of pill links where the active one is filled. Plain <a> by default; a tab's render draws a router's Link. orientation="vertical" stacks the same links in one column at the rail's width, for a SidebarLayout rail: the same pill, the same aria-current, one link per line.
items required- Type
NavTab[]
label requiredThe accessible name of the nav landmark.
- Type
string
orientation- Type
"horizontal" | "vertical"- Default
horizontal
Functions
navTabClassThe classes of one NavTabs pill (current or not, disabled drops the hover fill), for a router link that has to look like a tab.