Skip to content

Tabs

Switches between related panels of content in the same place. Use with TabList, Tab and TabPanel.

Navigationv0.1.0experimentalWCAG 2.2 AAView spec
Project summary and key numbers.
import { Tab, TabList, TabPanel, Tabs } from "@rdloom/react";

export default function TabsUnderlineExample() {
  return (
    <Tabs className="w-full max-w-lg">
      <TabList aria-label="Project">
        <Tab id="overview">Overview</Tab>
        <Tab id="activity">Activity</Tab>
        <Tab id="settings">Settings</Tab>
      </TabList>
      <TabPanel id="overview">Project summary and key numbers.</TabPanel>
      <TabPanel id="activity">Recent changes by the team.</TabPanel>
      <TabPanel id="settings">Name, members and permissions.</TabPanel>
    </Tabs>
  );
}

Installation

npx rdloom add tabs

Copies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes. It needs react-aria-components; add --install to install them.

Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/tabs.json

Works, but without upgrade tracking.

Usage

import { Tab, TabList, TabPanel, Tabs } from "@rdloom/react";

<Tabs className="w-full max-w-lg">
  <TabList aria-label="Project">
    <Tab id="overview">Overview</Tab>
    <Tab id="activity">Activity</Tab>
    <Tab id="settings">Settings</Tab>
  </TabList>
  <TabPanel id="overview">Project summary and key numbers.</TabPanel>
  <TabPanel id="activity">Recent changes by the team.</TabPanel>
  <TabPanel id="settings">Name, members and permissions.</TabPanel>
</Tabs>

Pill

Project summary and key numbers.
import { Tab, TabList, TabPanel, Tabs } from "@rdloom/react";

export default function TabsPillExample() {
  return (
    <Tabs variant="pill">
      <TabList aria-label="Project">
        <Tab id="overview">Overview</Tab>
        <Tab id="activity">Activity</Tab>
        <Tab id="settings">Settings</Tab>
      </TabList>
      <TabPanel id="overview">Project summary and key numbers.</TabPanel>
      <TabPanel id="activity">Recent changes by the team.</TabPanel>
      <TabPanel id="settings">Name, members and permissions.</TabPanel>
    </Tabs>
  );
}

Vertical

Project summary and key numbers.
import { Tab, TabList, TabPanel, Tabs } from "@rdloom/react";

export default function TabsVerticalExample() {
  return (
    <Tabs orientation="vertical" className="w-full max-w-lg">
      <TabList aria-label="Project">
        <Tab id="overview">Overview</Tab>
        <Tab id="activity">Activity</Tab>
        <Tab id="settings">Settings</Tab>
      </TabList>
      <TabPanel id="overview">Project summary and key numbers.</TabPanel>
      <TabPanel id="activity">Recent changes by the team.</TabPanel>
      <TabPanel id="settings">Name, members and permissions.</TabPanel>
    </Tabs>
  );
}

Controlled

This week's numbers.

Showing: week

import { useState } from "react";
import { Tab, TabList, TabPanel, Tabs } from "@rdloom/react";

export default function TabsControlledExample() {
  const [tab, setTab] = useState("week");
  return (
    <div className="flex w-full max-w-lg flex-col gap-2">
      <Tabs selectedKey={tab} onSelectionChange={(key) => setTab(key as string)}>
        <TabList aria-label="Range">
          <Tab id="day">Day</Tab>
          <Tab id="week">Week</Tab>
          <Tab id="month">Month</Tab>
        </TabList>
        <TabPanel id="day">Today's numbers.</TabPanel>
        <TabPanel id="week">This week's numbers.</TabPanel>
        <TabPanel id="month">This month's numbers.</TabPanel>
      </Tabs>
      <p className="text-sm">Showing: {tab}</p>
    </div>
  );
}

API Reference

Defined by the spec. Components also accept the props of the React Aria component they wrap.

PropTypeDefault
variant

Visual style of the tab list.

"underline" | "pill""underline"
orientation

Direction of the tab list.

"horizontal" | "vertical""horizontal"
selectedKey

Controlled selected tab.

Keynone
defaultSelectedKey

Initial tab when uncontrolled.

Keynone
isDisabled

Disables every tab.

booleanfalse
childrenrequired

A TabList followed by TabPanels.

nodenone
onSelectionChange

Called with the newly selected tab key.

(key: Key) => voidnone

Accessibility

Role tablist, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.

Keyboard

  • Arrow keys move between tabs
  • Home/End jump to first/last tab
  • Tab moves into the panel

Screen readers announce

  • Each tab: tab, its name, selected state and position (1 of 3)
  • Switching tabs announces the new tab; the panel's content is reachable next

What your code must do

  • Each tab controls exactly one panel
  • Tab list has an aria-label when there's no visible heading

Guidelines

Use it when

  • Grouping related views of the same object, like Overview / Activity / Settings

Avoid it when

  • Sequential steps: use a Stepper
  • Page navigation: use links

Don't

  • More than about 7 tabs
  • Tabs whose content depends on another tab's input

Design tokens

The semantic tokens this component uses. Change them once and every component follows; see Design tokens.

  • --rd-color-action-primary
  • --rd-color-border-default
  • --rd-color-surface-subtle
  • --rd-color-surface-raised
  • --rd-color-text-default
  • --rd-color-text-muted
  • --rd-color-focus-ring
  • --rd-radius-control