Tabs
A set of tabs whose active view comes from routing rather than tracked selection state.
View code
// The active tab comes from the URL, never from tracked state.
let activeIndex = sections.findIndex((section) => section.fragment === fragment);
let active = sections[activeIndex];
<Tabs>
<Tabs.List aria-label="Settings sections" activeIndex={activeIndex} tabSize="7rem">
{sections.map((section, index) => (
<Tabs.Tab
key={section.fragment}
href={section.fragment}
aria-selected={index === activeIndex}
mix={[
is("7rem"),
// A fragment navigation leaves no history entry the runtime reports,
// so the island reads the URL back on the press itself.
on<HTMLAnchorElement, "click">("click", () => {
fragment = section.fragment;
void handle.update();
}),
]}
>
{section.label}
</Tabs.Tab>
))}
</Tabs.List>
<Tabs.Panels>
<Tabs.Panel aria-label={active.heading} mix={[vstack({ gap: 2, align: "start" })]}>
<div mix={[hstack({ gap: 2, align: "center" })]}>
<span mix={[text("base"), weight("semibold")]}>{active.heading}</span>
<Badge variant="secondary">{active.fragment}</Badge>
</div>
<Text>{active.body}</Text>
</Tabs.Panel>
</Tabs.Panels>
</Tabs>Installation
An ordinary dependency: install it, import the theme once, and import the component where it is used.
npm add @sdxc/uiimport "@sdxc/ui/theme.css";Usage
import { Tabs } from "@sdxc/ui";Renders the root host: a plain <div> stacking Tabs.List above
Tabs.Panels, switching to a side-by-side row when orientation
is "vertical" and sharing that axis through component context.
Composition
The parts the component publishes, each a static property of the host.
TabsTabs.ListRenders the tab strip: arole="tablist"host laying Tabs.Tab links out in a row, or a column whenorientationis"vertical", with a sliding accent bar reading--ui-tab-indicator-*custom properties.Tabs.TabRenders a single tab: a native<a>carrying thetabrole, its own border edge kept transparent to reserve space for the sliding indicator.Tabs.PanelsRenders the panel region: a<div>growing to fill whatever space is left beside or beneath Tabs.List.Tabs.PanelRenders the current tab's content: a padded<div>carrying thetabpanelrole, focusable by default so a keyboard user can jump straight into it after activating a tab, with a focus-visible ring.
Examples
<Tabs orientation="vertical">
<Tabs.List aria-label={t("settings.sections")}>
<Tabs.Tab href="/settings/profile" aria-selected="true">{t("settings.profile")}</Tabs.Tab>
<Tabs.Tab href="/settings/billing" aria-selected="false">{t("settings.billing")}</Tabs.Tab>
</Tabs.List>
<Tabs.Panels>
<Tabs.Panel>{t("settings.profile.body")}</Tabs.Panel>
</Tabs.Panels>
</Tabs>Props
Read from the component's own types, so every prop, every default and every allowed value is listed.
| Prop | Type | Description |
|---|---|---|
orientation? | "horizontal" | "vertical" | Layout axis. Defaults to DEFAULT_ORIENTATION. |
Also accepts everything in TagProps<"div">.
Tabs.List
| Prop | Type | Description |
|---|---|---|
activeIndex? | number | Index of the currently active tab, for a route-driven tab bar with no client-side measurement of the active tab's bounds. Given with tabSize, Tabs.List computes its own indicator position. |
tabSize? | SizeValue | Every Tabs.Tab's fixed size along the enclosing Tabs's layout axis, required alongside activeIndex since the indicator's offset depends on how far apart the tabs sit. |
Also accepts everything in TagProps<"div">.
Tabs.Tab
Tabs.Panels
Also accepts everything in TagProps<"div">.
Tabs.Panel
Also accepts everything in TagProps<"div">.
Types
| Name | Values | What it decides |
|---|---|---|
Orientation | "horizontal" | "vertical" | Axis Tabs.List's links lay out along: a single row with the active indicator running beneath it, or a single column with the indicator running alongside it. |
Also exported
Other components this module publishes.
tabIndicatorMix