Cadence Clinical
Composites

Tabs

Views of one subject, one shown at a time. Tabs that do not fit wrap, and none is clipped.

GradeTested

Two appointments are booked.

Usage

import { Tabs, TabsContent, TabsList, TabsTrigger } from "@cadence-clinical/ui";

<Tabs defaultValue="appointments">
  <TabsList>
    <TabsTrigger value="appointments">Appointments</TabsTrigger>
    <TabsTrigger value="letters">Letters</TabsTrigger>
  </TabsList>
  <TabsContent value="appointments">...</TabsContent>
  <TabsContent value="letters">...</TabsContent>
</Tabs>;

Give each TabsTrigger and its TabsContent the same value. Set defaultValue on Tabs, or control it with value and onValueChange.

Tabs is built on the Base UI Tabs and accepts all of its props. It follows the shadcn Tabs and keeps its part names.

Tabs switch between views. To show and hide one section in place, such as earlier entries, use a Collapsible.

Use tabs for views of one subject, where a reader needs one at a time. Do not use tabs to hide something a reader must see alongside the rest: a view that is not shown is not rendered.

Variants

TabsList takes variant. The default list is a segmented control. The line list underlines the tab that is shown:

Two appointments are booked.
<TabsList variant="line">...</TabsList>

Set orientation="vertical" on Tabs to stack the tabs beside the view.

What Cadence changes

  • Tabs that do not fit wrap onto another row, whole. The shadcn list keeps one row, so on a narrow screen its tabs run past the edge of the screen. Base UI leaves overflow to you. Cadence wraps rather than scrolls sideways, because a view scrolled out of sight is a view a reader may not know is there. Each tab keeps its name on one line, and the tabs in a row share its width.
  • The view shows its focus. A view takes focus after its tab, so that a keyboard reaches a view with nothing of its own to focus. shadcn removes the outline from it. Here it gets the focus ring.
  • The shown tab is marked by shape as well as colour: a bordered fill in the default list and a rule in the line list. The rule uses the primary-text token, whose contrast is asserted on the page and on a card.
  • Height, padding and text follow the density. When the density is comfortable, each tab's target is 44px high.
  • The mark slides to the tab chosen with the pointer, over 150ms. Chosen from the keyboard, or with reduced motion, it is simply there: the change is what matters, not the trip. When the tabs move for any other reason, such as a resize, a turned phone, a change of density or a web font arriving, the mark moves with them at once, so it is never seen beside the shown tab on its way. The mark is a TabsIndicator, which TabsList renders for you. On the server-rendered page the shown tab draws its own mark until the indicator has measured it.
  • The focus ring is never faded.

Accessibility

  • The arrow keys move between tabs, and Enter or Space shows one. Moving through the tabs does not show each view on the way, which matters when a view loads data. Set activateOnFocus on TabsList for a view to follow focus, as in Base UI.
  • Each view is named by its tab.
  • A disabled tab cannot be shown. The arrow keys still reach it, so a screen reader user can find it and hear that it is disabled. Its words fade and its focus ring does not.

Props

PropOnTypeDefault
defaultValueTabsThe value of a tab
orientationTabs"horizontal" | "vertical""horizontal"
variantTabsList"default" | "line""default"
activateOnFocusTabsListbooleanfalse

On this page