oks-ui

How to use

Tabs

Tabs is a composable tabbed-navigation component with a roving-tabindex keyboard model built in — arrow keys move between tabs, only the active one sits in the tab order, and panels switch without a page reload. It can be driven declaratively with <Tab> children or dynamically from a data array via a render-function child.

import { Tabs, Tab } from "oks-ui";

Declarative tabs

For a fixed, known set of tabs, render <Tab> children directly — each one's title is the label, and its children are the panel content for that tab.

<Tabs aria-label="Account settings">
  <Tab title="Profile">
    <ProfileForm />
  </Tab>
  <Tab title="Billing">
    <BillingForm />
  </Tab>
  <Tab title="Security">
    <SecurityForm />
  </Tab>
</Tabs>

Dynamic tabs from data

When tabs come from an array (a list of projects, a set of API-driven categories), pass items and a render-function child instead of hand-writing a <Tab> per entry — the same underlying rendering, just data-driven.

<Tabs aria-label="Projects" items={projects}>
  {(project) => (
    <Tab key={project.id} title={project.name}>
      <ProjectSummary project={project} />
    </Tab>
  )}
</Tabs>

Controlled selection

Tabs manages its own selected tab by default (optionally seeded with defaultSelectedKey). Switch to selectedKey + onSelectionChange when something outside Tabs needs to react to or drive the active tab — syncing it to a URL query param, for instance.

const [tab, setTab] = useState("profile");

<Tabs selectedKey={tab} onSelectionChange={(key) => setTab(String(key))}>
  <Tab key="profile" title="Profile">...</Tab>
  <Tab key="billing" title="Billing">...</Tab>
</Tabs>

Placement and orientation

placement moves the tab list relative to its panels (top, bottom, start, end); isVertical switches the tab list itself to a vertical stack, useful paired with placement="start"/"end" for a sidebar-style tab layout.

<Tabs placement="start" isVertical aria-label="Settings">
  <Tab title="General">...</Tab>
  <Tab title="Notifications">...</Tab>
</Tabs>

Keyboard activation mode

keyboardActivation="automatic" (the default) selects a tab as soon as arrow-key focus reaches it. Switch to "manual" when moving focus shouldn't immediately trigger a (potentially expensive) panel change — the user arrows through tabs freely and only Enter/Space actually selects one.

Accessibility

  • Implements the standard tablist/tab/tabpanel ARIA pattern with roving tabindex — only the active tab is in the natural tab order; arrow keys move focus between the rest.
  • Disabled tabs (isDisabled, either on the whole Tabs or a single Tab) are skipped by arrow-key navigation, not just visually dimmed.
  • A Tab's accessible name comes from title; if title is a non-text ReactNode (an icon plus a label, say), pass titleValue with the plain-text equivalent so assistive tech still gets a real name.
  • Always pass Tabs its own aria-label (or aria-labelledby) describing what the tab set is for — "Account settings", "Projects" — since the tabs' own titles alone don't convey the group's purpose.

Props reference

All available props for Tabs. See the full component page for an interactive playground.

Props

children

ReactNode | ((item: T) => ReactElement)

Default:

Declarative <Tab> children, or a render function with items.

items

Iterable<T>

Default:

Dynamic items, used with a render-function children.

title

ReactNode

Default:

Tab label (required).

titleValue

string

Default:

Plain-text override of title, used for accessible naming when title is a node.

children

ReactNode

Default:

Panel content for this tab.

Best practices

  • Use Tabs for switching between views of related content within the same context — not as a substitute for real page-level navigation between unrelated sections.
  • Set destroyInactiveTabPanel when panels are expensive to keep mounted (a heavy chart, a form with its own side effects); leave it off (the default) when switching back to a tab should preserve its scroll position and in-progress state.
  • Prefer the declarative <Tab> children form for a small, fixed set of tabs, and the items + render-function form once the set is genuinely data-driven — trying to force a small static set through the dynamic form just adds indirection.
  • Keep tab titles short — a tab list isn't the place for a full sentence, and long titles fight the fixed-width layout most tab bars use.
View full API reference for Tabs