EQTY LabDocs

Tabs

A flexible, accessible component for organizing content into tabbed panels. Supports icons, customization, and multiple visual variants.

View as Markdown

Overview


The Tabs component provides a clean and accessible way to organize content into multiple panels. Built on top of Radix UI primitives, it includes smooth animations for tab switching and supports icons, custom styling, and different background variants.

The component automatically manages the active tab state and provides a smooth animated indicator that follows the active tab. Inactive tabs respond to hover, and the filled variant shares its track, indicator and timing with SegmentedControls. When the user prefers reduced motion, the indicator moves without sliding.

Usage

Import the component from the Equality UI library:

tsx
import { Tabs } from "@eqtylab/equality";

Basic usage requires an array of items:

tsx
<Tabs
  items={[
    {
      label: "Tab 1",
      value: "tab1",
      content: <div>Content for Tab 1</div>,
    },
    {
      label: "Tab 2",
      value: "tab2",
      content: <div>Content for Tab 2</div>,
    },
  ]}
/>

To control the active tab from outside, such as from the URL, pass value and update it in onValueChange. The indicator slides whenever value changes, including on back and forward navigation. Don’t remount Tabs with a key to change the tab: a remount places the indicator without sliding.

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

<Tabs value={tab} onValueChange={setTab} items={items} />;

Variants

Default

The default variant uses a transparent background with an animated underline indicator:

Example

tsx
<Tabs
  items={[
    {
      label: "Declarations",
      value: "declarations",
      content: <div>Declarations Content</div>,
    },
    {
      label: "Reviews",
      value: "reviews",
      content: <div>Reviews Content</div>,
    },
  ]}
/>

With icons

Add icons to tabs by including the icon property in each item. Icons can be provided as strings (icon name from Lucide React) or React elements:

Example

tsx
<Tabs
  items={[
    {
      label: "Declarations",
      value: "declarations",
      icon: "Pen",
      content: <div>Declarations Content</div>,
    },
    {
      label: "Reviews",
      value: "reviews",
      icon: "Eye",
      content: <div>Reviews Content</div>,
    },
  ]}
/>

With Fill

Use tabsListBackground="filled" to apply a filled background style to the tabs list. The active tab will have a filled indicator:

Example

tsx
<Tabs
  tabsListBackground="filled"
  items={[
    {
      label: "Implementation",
      value: "implementation",
      content: <div>Implementation Content</div>,
    },
    {
      label: "Monitoring",
      value: "monitoring",
      content: <div>Monitoring Content</div>,
    },
    {
      label: "Report",
      value: "report",
      content: <div>Report Content</div>,
    },
  ]}
/>

With Fill and Icons

Combine icons with the filled background variant for a more prominent tab design:

Example

tsx
<Tabs
  tabsListBackground="filled"
  items={[
    {
      label: "Implementation",
      value: "implementation",
      icon: "Shield",
      content: <div>Implementation Content</div>,
    },
    {
      label: "Monitoring",
      value: "monitoring",
      icon: "Activity",
      content: <div>Monitoring Content</div>,
    },
    {
      label: "Report",
      value: "report",
      icon: "FileChartLine",
      content: <div>Report Content</div>,
    },
  ]}
/>

With suffix

Use the suffix property to render any React component after a tab’s label. This is useful for badges, counts, or status indicators. The suffix works alongside icons and both background variants:

Example

tsx
<Tabs
  items={[
    {
      label: "Declarations",
      value: "declarations",
      suffix: (
        <Badge variant="neutral" size="sm">
          12
        </Badge>
      ),
      content: <div>Declarations Content</div>,
    },
    {
      label: "Reviews",
      value: "reviews",
      suffix: (
        <Badge variant="primary" size="sm">
          3
        </Badge>
      ),
      content: <div>Reviews Content</div>,
    },
  ]}
/>

Forwarding trigger props

Use the triggerProps field on an item to forward props onto that individual tab’s trigger. This is useful for attaching event handlers to a specific tab — for example, warming a destination’s data on hover or focus (prefetching) when the user shows intent toward that tab.

Any props are spread onto the trigger before the component’s own value and styling, so tab selection and internal classes are never overridden, and a custom className is merged with the internal ones. Event handlers compose with the component’s built-in behavior, so tab activation and keyboard navigation are unchanged.

tsx
<Tabs
  items={[
    {
      label: "Declarations",
      value: "declarations",
      content: <div>Declarations Content</div>,
      triggerProps: {
        onPointerEnter: () => prefetchDeclarations(),
        onFocus: () => prefetchDeclarations(),
      },
    },
    {
      label: "Reviews",
      value: "reviews",
      content: <div>Reviews Content</div>,
      triggerProps: {
        onPointerEnter: () => prefetchReviews(),
        onFocus: () => prefetchReviews(),
      },
    },
  ]}
/>

Props

NameDescriptionTypeDefaultRequired
idDeprecated and ignored. Kept so existing code compilesstring-❌
itemsArray of tab items (see TabItem structure below)Array<TabItem>-✅
valueThe active tab, when controlledstring-❌
defaultValueThe value of the tab that should be active by defaultstringFirst item❌
tabsListBackgroundBackground style for the tabs listtransparent, filledtransparent❌
onValueChangeCallback fired when the active tab changes(value: string) => void-❌
staticRenderRender for a page where React never runs on the clientbooleanfalse❌

Statically Rendered Tabs

staticRender is for when the component must work statically without client-side rendering, such as in @eqtylab/docs. Leave the prop off for ordinary interactive use.

TabItem Structure

Each item in the items array should have the following structure:

NameDescriptionTypeDefaultRequired
labelText label displayed on the tab triggerstring-✅
valueUnique value that identifies this tabstring-✅
iconIcon to display alongside the label (Lucide name or element)React.ReactElement, string-❌
suffixContent rendered after the label (e.g. a badge or count)React.ReactNode-❌
contentContent to display when this tab is activeReact.ReactNode-✅
triggerPropsProps forwarded onto this tab’s trigger (see Forwarding trigger props)Omit<ComponentPropsWithoutRef<typeof TabsTrigger>, "value">-❌