Tabs

Swaps between sibling panels without leaving the page.

import { Tabs } from "@usefragments/ui";Source

Preview

<Tabs defaultValue="overview" variant="ghost">
  <Tabs.List>
    <Tabs.Tab value="overview">Overview</Tabs.Tab>
    <Tabs.Tab value="analytics">Analytics</Tabs.Tab>
    <Tabs.Tab value="settings">Settings</Tabs.Tab>
  </Tabs.List>

Usage

Use when

  • Sibling sections of equal weight
  • Cutting scroll on a long page
  • Settings split by category
  • One data set seen several ways

Avoid when

  • Moving between pages (use Sidebar or header navigation)
  • Steps in an order (use Stepper)
  • Content the user compares side by side
  • More options than fit in a row (use Select)

Implementation notes

  • One or two words per label
  • Order by how often each panel is used
  • Past five or six tabs, split the page instead
  • Keep panels comparable in depth
  • Soft (segmented) inside cards and panels, ghost at page level
  • Counts go in the count slot, not in the label

Installation

pnpm add @usefragments/ui

Import styles once from @usefragments/ui/styles. See the installation guide for framework and theme setup.

Anatomy

Import Tabs once, then compose its parts with dot notation.

  • Tabs.List
  • Tabs.Tab
  • Tabs.Panel

A 2px ink underline marks the open panel.

<Tabs defaultValue="overview" variant="ghost">
  <Tabs.List>
    <Tabs.Tab value="overview">Overview</Tabs.Tab>
    <Tabs.Tab value="analytics">Analytics</Tabs.Tab>
    <Tabs.Tab value="settings">Settings</Tabs.Tab>
  </Tabs.List>
  <Tabs.Panel value="overview">
    <p>Overview content goes here.</p>
  </Tabs.Panel>
  <Tabs.Panel value="analytics">
    <p>Analytics content goes here.</p>
  </Tabs.Panel>
  <Tabs.Panel value="settings">
    <p>Settings content goes here.</p>
  </Tabs.Panel>
</Tabs>

Examples

With Disabled

An unavailable panel stays visible but unreachable.

<Tabs defaultValue="general" variant="ghost">
  <Tabs.List>
    <Tabs.Tab value="general">General</Tabs.Tab>
    <Tabs.Tab value="security">Security</Tabs.Tab>
    <Tabs.Tab value="billing" disabled>Billing</Tabs.Tab>
  </Tabs.List>

Panel Tabs

Counts, a changed dot and actions outside the tablist.

import { Plus } from "@phosphor-icons/react";

const [tab, setTab] = React.useState("layers");
const reveal = () => {};

<Tabs value={tab} onValueChange={setTab} onReselect={reveal} variant="soft" size="md">

API reference

Component props
Prop
Type
Default
Description
childrenRequiredReact.ReactNodeNot setTab list and panels (use Tabs.List, Tabs.Tab, Tabs.Panel)
defaultValuestring | undefinedNot setInitially active tab (uncontrolled)
List.actionsReact.ReactNodeNot setControls beside the tabs, outside the tablist and its arrow-key navigation.
List.activateOnFocusboolean | undefinedNot setArrow keys open the tab they reach, instead of only moving focus.
List.childrenRequiredReact.ReactNodeNot set—
onReselect((value: TabValue) => void) | undefinedNot setCalled when the open tab is pressed again (it may close what covers its panel)
onValueChange((value: TabValue, eventDetails: TabsChangeEventDetails) => void) | undefinedNot setCalled when active tab changes
Panel.childrenRequiredReact.ReactNodeNot set—
Panel.classNamestring | undefinedNot set—
Panel.valueRequiredstringNot set—
sizeTabsSize | undefinedsmmdNot setTab height: sm 28, md 32. Defaults to the component-default control size
Tab.childrenRequiredReact.ReactNodeNot set—
Tab.classNamestring | undefinedNot set—
Tab.countReact.ReactNodeNot setA quieter figure after the name (how many there are, or `…` while counting).
Tab.dotstring | boolean | undefinedNot setA dot after the name: something changed in the tab. A string is read to screen readers.
Tab.valueRequiredstringNot set—
valuestring | undefinedNot setControlled active tab value
variantTabsVariant | undefinedsoftghostghostTab row chrome: ghost draws a 2px ink underline under the open tab, soft is the segmented control (band track, lifted thumb)

Accessibility

  • Arrow keys move between tabs; activateOnFocus opens the tab they reach
  • Actions sit outside the tablist, so arrow keys skip them
  • A dot's words are read with the tab name
  • The panel takes focus with the shared focus ring
  • SelectalternativeUse Select for many options in compact space
  • MenualternativeUse Menu for action-based navigation

Next steps