A horizontal tab navigation component with full keyboard support and ARIA compliance. Tabs can render as links or buttons and support custom styling, add-ons, and label rendering.
| Prop | Type | Default | Description |
|---|---|---|---|
| aria-controls | string | Required | ID of the content panel this tab controls |
| tabs | Tab[] | Required | Array of tab configurations |
| variant | "underline" | "segmented" | "underline" | Visual selection treatment |
| labelSize | Size | "large" | Text size for tab labels |
| iconSize | Size | "medium" | Size of tab icons |
| tabPadding | Padding | "medium" | Padding inside each tab |
| gap | Gap | "none" | Space between tabs |
| jssTab | JSS | (data) => JSS | - | Custom styles for tabs (can be conditional on selected state) |
| labelRenderer | (label: string) => ReactNode | - | Custom label rendering function |
| Property | Type | Default | Description |
|---|---|---|---|
| label | string | Required | Tab display text |
| icon | IconType | - | Icon displayed with the label |
| href | string | - | Link URL (renders as link if provided) |
| onClick | () => void | - | Click handler (renders as button if no href) |
| selected | boolean | false | Whether this tab is currently selected |
| addOn | ReactNode | - | Additional element (e.g., badge, icon) |
| addOnPosition | "start" | "end" | "start" | Position of add-on relative to label |
<Tabs
aria-controls="panel-1"
tabs={[
{ href: "/Tabs", selected: true, label: "Tab 1" },
{ href: "/Tabs", selected: false, label: "Tab 2" },
{ href: "/Tabs", selected: false, label: "Tab 3" },
]}
/>
Add badges, icons, or other elements to tabs:
// Badge before label (default)
<Tabs
aria-controls="panel"
tabs={[
{ href: "/messages", selected: false, label: "Messages" },
{
href: "/notifications",
selected: true,
label: "Notifications",
addOn: <Badge color="negative" size="small" />,
},
]}
/>
// Badge after label
<Tabs
aria-controls="panel"
tabs={[
{
href: "/new",
selected: true,
label: "New",
addOn: <Badge color="highlight" size="medium" />,
addOnPosition: "end",
},
]}
/>
Omit href to render tabs as buttons. Useful for in-page tab switching:
const [activeTab, setActiveTab] = useState("details");
<Tabs
aria-controls="content-panel"
tabs={[
{
selected: activeTab === "details",
label: "Details",
onClick: () => setActiveTab("details"),
},
{
selected: activeTab === "reviews",
label: "Reviews",
onClick: () => setActiveTab("reviews"),
},
{
selected: activeTab === "related",
label: "Related",
onClick: () => setActiveTab("related"),
},
]}
/>
Use labelRenderer to customize how tab labels are displayed:
<Tabs
aria-controls="panel"
labelRenderer={(label) => (
<>
<Icon icon="star" size="small" />
{label}
</>
)}
tabs={[
{ href: "/featured", selected: true, label: "Featured" },
{ href: "/popular", selected: false, label: "Popular" },
]}
/>
Use jssTab for custom styling. It can be a static style object or a function that receives the tab's selected state:
// Conditional styling based on selection
<Tabs
aria-controls="panel"
jssTab={(data) => ({
borderBottom: data.selected
? "3px solid var(--highlight)"
: "3px solid transparent",
backgroundColor: data.selected
? "var(--light-highlight)"
: "transparent",
})}
tabs={tabs}
/>
// Static styling for all tabs
<Tabs
aria-controls="panel"
jssTab={{ borderRadius: 8 }}
tabs={tabs}
/>
Tabs support full keyboard navigation:
The Tabs component follows WAI-ARIA tab patterns:
<Tabs
aria-controls="settings-panel"
tabs={tabs}
/>
<div id="settings-panel" role="tabpanel">
{/* Panel content */}
</div>