Navigation
Tabs
One view at a time from a small set. Radix supplies the roving tabindex and the aria-controls wiring, the parts that make a tab strip a tab strip rather than a row of buttons.
Basic
Focus a tab, then use the arrow keys. The strip is one tab stop; Tab again reaches the panel.
Color, spacing, radius, type, shadow and motion. Four consumption formats generated from one source.
<Tabs.Root defaultValue="tokens"> <Tabs.List> <Tabs.Trigger value="tokens">Tokens</Tabs.Trigger> <Tabs.Trigger value="base">Base</Tabs.Trigger> <Tabs.Trigger value="components">Components</Tabs.Trigger> <Tabs.Trigger value="archived" disabled>Archived</Tabs.Trigger> </Tabs.List> <Tabs.Panel value="tokens"> <Text size={5} color="assistive"> Color, spacing, radius, type, shadow and motion. Four consumption formats generated from one source. </Text> </Tabs.Panel> <Tabs.Panel value="base"> <Text size={5} color="assistive"> Eighteen headless primitives. Radix where WAI-ARIA already specifies the contract. </Text> </Tabs.Panel> <Tabs.Panel value="components"> <Text size={5} color="assistive"> Twenty-three styled components, replaceable by design. </Text> </Tabs.Panel></Tabs.Root>Size
Two, set on the list so every tab in a strip matches. 15 labels a section, 17 labels a page.
{(['m', 'l'] as const).map(size => ( <Tabs.Root key={size} defaultValue="tokens"> <Tabs.List size={size}> <Tabs.Trigger value="tokens">Tokens</Tabs.Trigger> <Tabs.Trigger value="base">Base</Tabs.Trigger> <Tabs.Trigger value="components">Components</Tabs.Trigger> </Tabs.List> </Tabs.Root>))}Fill
Divides the strip evenly instead of sizing each tab to its label. For a strip that is the width of the thing it belongs to.
<Tabs.Root defaultValue="tokens"> <Tabs.List fill> <Tabs.Trigger value="tokens">Tokens</Tabs.Trigger> <Tabs.Trigger value="base">Base</Tabs.Trigger> <Tabs.Trigger value="components">Components</Tabs.Trigger> </Tabs.List></Tabs.Root>The whole strip is one tab stop. Tab into it, then arrow between tabs and Tab again to reach the panel. That is what a screen reader user expects, and the part most hand-rolled tab strips miss.
Parts
Import it as a namespace, and the parts work on either side of the server boundary.
import * as Tabs from '@minuk-hwang-design-system/components-react/tabs';| Part | What it is |
|---|---|
Tabs.Root | Owns the selected value. |
Tabs.List | The strip, with the rail under it. |
Tabs.Trigger | One tab. Carries the indicator. |
Tabs.Panel | Its content. Named for what it is; Radix calls this Content, which reads oddly beside Dialog.Content. |
Props
| Prop | Type | Default | Notes |
|---|---|---|---|
Root.value | string | — | Controlled selection. |
Root.defaultValue | string | — | Uncontrolled initial tab. |
Root.onValueChange | (value: string) => void | — | Fires with the chosen tab's value. |
List.fill | boolean | false | Divides the strip evenly between the tabs. |
List.size | 'm' | 'l' | 'm' | 15 or 17. On the list, so every tab in a strip matches. |
Root.activationMode | 'automatic' | 'manual' | 'automatic' | Automatic selects on arrow; manual waits for Enter. |