Tabs
A React source recipe with Base UI behavior and the public TIS Tabs classes and tokens.
Design
Functional preview
Running with the React source recipe distributed through shadcn and based on Base UI.
Open the React · shadcn/Base UI playground
Anatomy
Panel content goes here.
4.ds-tabs) — container with role="tablist".2 Tab (
.ds-tab) — individual tab button with role="tab".3 Active indicator — bottom border on the active tab.
4 Tab panel — content area with
role="tabpanel".
Default tabs
One tab is active, the others are in their default state. Each tab controls a corresponding panel.
This is the Overview panel content. It provides a summary of your account status and recent activity.
Settings panel for profile, notifications preferences, and account security options.
Activity panel listing recent logins, changes, and audit events.
With disabled tab
A disabled tab cannot be interacted with and is skipped during keyboard navigation.
General settings for your account: display name, language, and timezone preferences.
Multiple tabs
Tabs scroll horizontally when they overflow the container.
Dashboard panel showing project metrics, recent commits, and team activity overview.
Token mapping
| Property | Token | CSS variable |
|---|---|---|
| text (inactive) | component.tabs.label.color.default | --ds-tabs-label-color-default |
| text (hover) | component.tabs.label.color.hover | --ds-tabs-label-color-hover |
| text (active) | component.tabs.label.color.active | --ds-tabs-label-color-active |
| text (disabled) | component.tabs.label.color.disabled | --ds-tabs-label-color-disabled |
| active indicator | component.tabs.indicator.fill.active | --ds-tabs-indicator-fill-active |
| divider | semantic.overlay.default | --ds-overlay-default |
| list border | component.tabs.list.border-width.default | --ds-tabs-list-border-width-default |
| item padding | component.tabs.item.{padding-x,padding-top}.default | --ds-tabs-item-padding-*-default |
| gap to indicator | component.tabs.item.indicator-gap.default | --ds-tabs-item-indicator-gap-default |
| indicator height | component.tabs.indicator.height.default | --ds-tabs-indicator-height-default |
| label typography | component.tabs.label.{font-size,line-height,font-weight,letter-spacing}.default | --ds-tabs-label-*-default |
| panel padding | component.tabs.panel.padding-y.default | --ds-tabs-panel-padding-y-default |
| focus ring | component.focus-ring.{color.default,width} | --ds-focus-ring-color-default, --ds-focus-ring-width |
| focus ring radius | component.tabs.focus-ring.radius.default | --ds-tabs-focus-ring-radius-default |
Usage
When to use
Best practices
Content guidelines
| Rule | Example |
|---|---|
| Use nouns as labels | "Overview", "Settings", "Activity" — not "View overview" or "Go to settings" |
| Same hierarchy level | All tabs should represent parallel categories of the same content |
| No icons without text | Always include a text label — icons alone are ambiguous in tab context |
| Keep labels short (1-2 words) | "Pull Requests" — not "View all pull requests in this project" |
Related
React composition
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/components/ui/tabs"
<Tabs defaultValue="overview">
<TabsList aria-label="Seções do projeto">
<TabsTrigger value="overview">Visão geral</TabsTrigger>
<TabsTrigger value="team">Equipe</TabsTrigger>
</TabsList>
<TabsContent value="overview">Resumo do projeto.</TabsContent>
<TabsContent value="team">Equipe do projeto.</TabsContent>
</Tabs>Implementation
- Status
- Beta
- Distribution
- Source via shadcn
Installation
Configure the namespace once in the React integration guide.
npx shadcn@latest add @tis/tabspnpm dlx shadcn@latest add @tis/tabsyarn dlx shadcn@latest add @tis/tabsbunx --bun shadcn@latest add @tis/tabsPublic contract
- Registry item
@tis/tabs- Provider
- Base UI
- Distribution
- Source copied into the application
- Status
- Beta
Accessibility
Keyboard interaction
| Key | Action |
|---|---|
Tab | Moves focus into the tab list, landing on the active tab |
Arrow Left / Arrow Right | Moves to previous / next tab (auto-activates) |
Home | Moves to the first tab |
End | Moves to the last tab |
Tab (from list) | Moves focus into the active panel content |
Disabled tabs are skipped during arrow key navigation. Only the active tab is in the Tab sequence (tabindex="0"); all others have tabindex="-1".
Accessibility
| WCAG criterion | Requirement | Status |
|---|---|---|
| 4.1.2 Name, Role, Value (A) | role="tablist", role="tab", role="tabpanel". aria-selected on tabs. aria-controls / aria-labelledby linking tabs to panels. | ✓ |
| 2.1.1 Keyboard (A) | Arrow keys navigate between tabs. Tab enters and exits the tab list. | ✓ |
| 2.4.11 Focus Appearance (AA) | Focus ring visible on the active tab when focused. | ✓ |
role="tablist" — on the tab list container.role="tab" — on each tab button.role="tabpanel" — on each content panel.aria-selected="true|false" — indicates the active tab.aria-controls — on each tab, pointing to the corresponding panel ID.aria-labelledby — on each panel, pointing to the corresponding tab ID.aria-disabled="true" — on disabled tabs.
Output responsibility
Preserve Base UI semantics and test real content, focus visibility, and accessible names in the consuming application.
Validation evidence
Covered by its independent Storybook output plus browser, responsive, keyboard, Axe, and bundle checks.