Tabs
Set of tabs that organizes content into switchable panels. A
controlled component: the consumer keeps selectedIndex in state and is notified
via onChange when a different tab is clicked.
Each tab can have its own color (bottom indicator), arbitrary content
(ReactNode) and an optional handleClick callback fired before onChange
— useful for event tracking.
When to use
| ✅ Use when… | 🚫 Avoid when… |
|---|---|
|
|
Basic usage
import { Tabs } from '@apollion-dsi/core/data-display/tabs';
import { bell } from '@apollion-dsi/core/icons';
const Example = () => {
const [index, setIndex] = React.useState(0);
return (
<Tabs
selectedIndex={index}
onChange={setIndex}
tabs={[
{ tabTitle: 'First Tab', content: <FirstPage /> },
{ tabTitle: 'Second Tab', content: <SecondPage /> },
{ tabTitle: 'Third Tab', content: <ThirdPage /> },
]}
/>
);
};Changing the bar color
The bottom indicator color follows the theme palette and is set per tab.
<Tabs
tabs={[
{ tabTitle: 'First', content: <Page1 />, color: 'warning' },
{ tabTitle: 'Second', content: <Page2 />, color: 'danger' },
]}
/>Indicator
The selected-tab indicator is a 2px segment on the header rule, painted with
the selected tab's color (primary by default). It is a dedicated
pseudo-element, not a border-bottom, so linked buttons and layout props
cannot zero it; inactive tabs keep the neutral hairline only.
Always active
To keep a tab's indicator always on, use isActive:
<Tabs
tabs={[
{
tabTitle: 'First',
content: <Page1 />,
},
{
isActive: true,
color: 'warning',
tabTitle: 'Promo',
content: <Promo />,
},
]}
/>Custom title
tabTitle accepts any ReactNode — combine icons, badges and
tooltips if the tab needs more signaling:
<Tabs
tabs={[
{
tabTitle: (
<Flex flexDirection="row" wrap="wrap" gap="micro" alignItems="center">
<Icon icon={bell} size="small" />
<Text>Notifications</Text>
</Flex>
),
content: <NotificationsPanel />,
},
]}
/>Accessibility
Follows the ARIA Tabs pattern (opens in a new tab) with no extra configuration.
| Key | Action |
|---|---|
Tab | Enters and leaves the tab set as one stop — not one per tab |
← / → | Moves between tabs, wrapping at the ends |
Home / End | First / last tab |
The header is a tablist, each button is a tab with aria-selected, and the content is a tabpanel labeled by the active tab. Focus follows selection.
Do not override
roleortabIndexviabuttonProps: it breaks keyboard navigation.
Properties
Prop | Type | Default | Description |
|---|---|---|---|
color | string | — | Theme token (`theme.colors`) used as `color`. Has lower precedence than
`contrast`: when both are provided, the contrast calculation wins.
Stays `string` (not the typed union): `color` is also an HTML attribute
(`<li color>`, etc.) and interfaces extending native props require
identical types on the name collision. |
containment | string | — | Containment marker for the `cq` channel: emits
`container-type` (+ `container-name` when the string form carries one,
e.g. `'inline-size card'`). Apply on the immediate wrapper of the
adapting component — NEVER on page-level shells. |
legibility | "on-photo" | — | Reading-shadow preset for text over a photographic background
(`on-photo`). Replaces the inline `style={{ textShadow }}` in the
consumer (brasil_2030 radar, gap A3). Token emission is deferred until a
2nd consumer asks for the raw var (see backlog). |
onChange * | (index: number) => void | — | Fired when clicking a tab other than the current one. |
pageShell | boolean | — | Centers the element and caps the width at `theme.layout.pageMaxWidth`.
The page shell of a classic centered layout. |
readable | number | boolean | — | Makes the `color` legible against the page surface: `true` = WCAG AA
(4.5), a number sets a custom floor. Ignored with `contrast`.
See the Layout Props concept page for the full semantics. |
selectedIndex | number | 0 | Index of the active tab. |
tabContentProps | DefaultFlexInterface | — | Extra `Flex` props applied to the active tab's content container. |
tabHeaderProps | DefaultFlexInterface | — | Extra `Flex` props applied to the tabs header. |
tabs * | TabType[] | — | Tab definitions (title + content + options). |
transform | string | — | CSS `transform` value (e.g. `translate(-50%, -33px)`). Never leaks
to the DOM as an attribute. |
Beyond the props above, every component accepts the layout props (spacing, color, flex/grid, sizing, border) — not repeated here.
See also
- Storybook story: Components / Tabs