@xsolla/xui-tabs
v0.151.0
Published
A cross-platform React tab list with two visual variants — `line` (default underlined) and `segmented` (button-group with sliding indicator on web). Implements the WAI-ARIA tablist pattern with full keyboard navigation.
Readme
Tabs
A cross-platform React tab list with two visual variants — line (default underlined) and segmented (button-group with sliding indicator on web). Implements the WAI-ARIA tablist pattern with full keyboard navigation.
Installation
npm install @xsolla/xui-tabsImports
import {
Tabs,
TabPanel,
type TabItemType,
type TabsProps,
type TabPanelProps,
} from '@xsolla/xui-tabs';Quick start
import * as React from 'react';
import { Tabs, TabPanel } from '@xsolla/xui-tabs';
export default function QuickStart() {
const [active, setActive] = React.useState('overview');
const tabs = [
{ id: 'overview', label: 'Overview' },
{ id: 'pricing', label: 'Pricing' },
];
return (
<div>
<Tabs id="qs" tabs={tabs} activeTabId={active} onChange={setActive} />
<TabPanel id="overview" tabsId="qs" hidden={active !== 'overview'}>Overview content</TabPanel>
<TabPanel id="pricing" tabsId="qs" hidden={active !== 'pricing'}>Pricing content</TabPanel>
</div>
);
}API Reference
<Tabs>
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| tabs | TabItemType[] | — | Required. Tab definitions. |
| activeTabId | string | — | Currently active tab. |
| onChange | (id: string) => void | — | Called when tab selection changes. |
| size | "xl" \| "lg" \| "md" \| "sm" | "md" | Size variant. |
| variant | "line" \| "segmented" | "line" | Visual style. |
| alignLeft | boolean | true | Line variant only — left vs centre alignment. |
| stretched | boolean | false | Segmented variant only — distribute items at equal width. |
| activateOnFocus | boolean | true | Activate the focused tab automatically; otherwise require Enter/Space. |
| id | string | — | Root id used to derive *-tablist, *-tab-{id}, and *-tabpanel-{id} ids. |
| aria-label | string | — | Accessible label for the tab list. |
| aria-labelledby | string | — | ID of an element labelling the tab list. |
| testID | string | — | Test identifier. |
Inherits ThemeOverrideProps (themeMode, themeProductContext).
<TabPanel>
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| id | string | — | Required. Matches the corresponding tab.id. |
| tabsId | string | — | Required. Parent Tabs id. |
| hidden | boolean | false | Whether the panel is hidden. |
| children | ReactNode | — | Required. Panel content. |
| aria-label | string | — | Accessible label for the panel. |
| testID | string | — | Test identifier. |
TabItemType
interface TabItemType {
id: string;
label: string;
icon?: React.ReactNode;
counter?: string | number;
counterPalette?: "brand" | "tertiary" | "default"; // line variant only; default "brand"
badge?: boolean | string | number;
disabled?: boolean;
"aria-label"?: string;
}Exported types
| Type | Description |
| --- | --- |
| TabsProps | Props for <Tabs>. |
| TabPanelProps | Props for <TabPanel>. |
| TabItemType | Tab definition. |
Keyboard navigation
| Key | Action |
| --- | --- |
| Arrow Right / Down | Move to next enabled tab. |
| Arrow Left / Up | Move to previous enabled tab. |
| Home | Jump to first enabled tab. |
| End | Jump to last enabled tab. |
| Enter / Space | Activate the focused tab when activateOnFocus={false}. |
Examples
Tabs with icons and counters
import * as React from 'react';
import { Tabs } from '@xsolla/xui-tabs';
import { Home, User, Settings } from '@xsolla/xui-icons-base';
export default function IconTabs() {
const [active, setActive] = React.useState('home');
const tabs = [
{ id: 'home', label: 'Home', icon: <Home size={16} /> },
{ id: 'profile', label: 'Profile', icon: <User size={16} />, counter: 5 },
{ id: 'settings', label: 'Settings', icon: <Settings size={16} /> },
];
return <Tabs tabs={tabs} activeTabId={active} onChange={setActive} />;
}Segmented variant
import * as React from 'react';
import { Tabs } from '@xsolla/xui-tabs';
export default function SegmentedTabs() {
const [active, setActive] = React.useState('daily');
const tabs = [
{ id: 'daily', label: 'Daily' },
{ id: 'weekly', label: 'Weekly' },
{ id: 'monthly', label: 'Monthly' },
];
return <Tabs tabs={tabs} activeTabId={active} onChange={setActive} variant="segmented" />;
}Stretched segmented
import * as React from 'react';
import { Tabs } from '@xsolla/xui-tabs';
export default function StretchedTabs() {
const [active, setActive] = React.useState('login');
const tabs = [
{ id: 'login', label: 'Log in' },
{ id: 'signup', label: 'Sign up' },
];
return <Tabs tabs={tabs} activeTabId={active} onChange={setActive} variant="segmented" stretched />;
}Disabled tab
import * as React from 'react';
import { Tabs } from '@xsolla/xui-tabs';
export default function DisabledTab() {
const [active, setActive] = React.useState('a');
const tabs = [
{ id: 'a', label: 'Active' },
{ id: 'b', label: 'Disabled', disabled: true },
{ id: 'c', label: 'Another' },
];
return <Tabs tabs={tabs} activeTabId={active} onChange={setActive} />;
}Manual activation
import * as React from 'react';
import { Tabs } from '@xsolla/xui-tabs';
export default function ManualActivation() {
const [active, setActive] = React.useState('one');
const tabs = [
{ id: 'one', label: 'One' },
{ id: 'two', label: 'Two' },
{ id: 'three', label: 'Three' },
];
return (
<Tabs
tabs={tabs}
activeTabId={active}
onChange={setActive}
activateOnFocus={false}
/>
);
}Full panels
import * as React from 'react';
import { Tabs, TabPanel } from '@xsolla/xui-tabs';
export default function FullTabs() {
const [active, setActive] = React.useState('dashboard');
const tabs = [
{ id: 'dashboard', label: 'Dashboard' },
{ id: 'analytics', label: 'Analytics', counter: 5 },
{ id: 'reports', label: 'Reports' },
];
return (
<div>
<Tabs id="main" tabs={tabs} activeTabId={active} onChange={setActive} />
<div style={{ padding: 24, border: '1px solid #eee', borderTop: 'none' }}>
<TabPanel id="dashboard" tabsId="main" hidden={active !== 'dashboard'}>Dashboard content.</TabPanel>
<TabPanel id="analytics" tabsId="main" hidden={active !== 'analytics'}>Analytics content.</TabPanel>
<TabPanel id="reports" tabsId="main" hidden={active !== 'reports'}>Reports content.</TabPanel>
</div>
</div>
);
}Accessibility
- Implements the WAI-ARIA tablist pattern with
role="tablist",role="tab", androle="tabpanel". aria-selected,aria-controls, andaria-labelledbylink tabs and panels.- Roving
tabIndexmoves focus to the active tab; arrow keys cycle through enabled tabs. - The segmented variant's sliding indicator is marked
aria-hidden.
Platform Support
- Web — segmented variant uses a smooth sliding active indicator backed by
ResizeObserver. - React Native — segmented variant falls back to per-tab background highlighting (no animation).
