Repo

Usage

<div class="zui-tabs" data-orientation="horizontal">
<div class="zui-tabs-list" role="tablist" aria-label="Profile sections">
  <button class="zui-tabs-trigger" type="button" role="tab" aria-selected="true">Account</button>
  <button class="zui-tabs-trigger" type="button" role="tab" aria-selected="false">Security</button>
  <button class="zui-tabs-trigger" type="button" role="tab" aria-selected="false">Billing</button>
</div>

<div class="zui-tabs-content" role="tabpanel">Account settings</div>
</div>

Underline variant

Use variant="underline" on both TabsList and TabsTrigger when you want a simpler tab bar without the bordered container.

import { Tabs, TabsList, TabsTrigger, TabsContent } from '@mrmartineau/zui/react'

<Tabs defaultValue="overview">
<TabsList variant="underline" aria-label="Project sections">
  <TabsTrigger variant="underline" value="overview">Overview</TabsTrigger>
  <TabsTrigger variant="underline" value="activity">Activity</TabsTrigger>
  <TabsTrigger variant="underline" value="settings">Settings</TabsTrigger>
</TabsList>

<TabsContent value="overview">Overview panel</TabsContent>
<TabsContent value="activity">Recent activity</TabsContent>
<TabsContent value="settings">Project settings</TabsContent>
</Tabs>

Vertical tabs

import { Tabs, TabsList, TabsTrigger, TabsContent } from '@mrmartineau/zui/react'

<Tabs defaultValue="profile" orientation="vertical">
<TabsList aria-label="Settings sections">
  <TabsTrigger value="profile">Profile</TabsTrigger>
  <TabsTrigger value="notifications">Notifications</TabsTrigger>
  <TabsTrigger value="team">Team</TabsTrigger>
</TabsList>

<TabsContent value="profile">Profile preferences</TabsContent>
<TabsContent value="notifications">Notification preferences</TabsContent>
<TabsContent value="team">Team settings</TabsContent>
</Tabs>

Vertical underline tabs

import { Tabs, TabsList, TabsTrigger, TabsContent } from '@mrmartineau/zui/react'

<Tabs defaultValue="profile" orientation="vertical">
<TabsList variant="underline" aria-label="Settings sections">
  <TabsTrigger variant="underline" value="profile">Profile</TabsTrigger>
  <TabsTrigger variant="underline" value="notifications">Notifications</TabsTrigger>
  <TabsTrigger variant="underline" value="team">Team</TabsTrigger>
</TabsList>

<TabsContent value="profile">Profile preferences</TabsContent>
<TabsContent value="notifications">Notification preferences</TabsContent>
<TabsContent value="team">Team settings</TabsContent>
</Tabs>

Manual activation

Use activationMode="manual" when arrow keys should move focus without switching panels until Enter or Space.

import { Tabs, TabsList, TabsTrigger, TabsContent } from '@mrmartineau/zui/react'

<Tabs defaultValue="overview" activationMode="manual">
<TabsList aria-label="Project sections">
  <TabsTrigger value="overview">Overview</TabsTrigger>
  <TabsTrigger value="activity">Activity</TabsTrigger>
  <TabsTrigger value="settings">Settings</TabsTrigger>
</TabsList>

<TabsContent value="overview">Overview panel</TabsContent>
<TabsContent value="activity">Recent activity</TabsContent>
<TabsContent value="settings">Project settings</TabsContent>
</Tabs>

Controlled tabs

Use value with onValueChange when tab state should be controlled by application state.

import { useState } from 'react'
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@mrmartineau/zui/react'

export function Example() {
const [tab, setTab] = useState('activity')

return (
  <Tabs value={tab} onValueChange={setTab}>
    <TabsList aria-label="Team sections">
      <TabsTrigger value="overview">Overview</TabsTrigger>
      <TabsTrigger value="activity">Activity</TabsTrigger>
      <TabsTrigger value="members">Members</TabsTrigger>
    </TabsList>

    <TabsContent value="overview">Team overview</TabsContent>
    <TabsContent value="activity">Recent activity</TabsContent>
    <TabsContent value="members">Member list</TabsContent>
  </Tabs>
)
}

Disabled tabs

import { Tabs, TabsList, TabsTrigger, TabsContent } from '@mrmartineau/zui/react'

<Tabs defaultValue="design">
<TabsList aria-label="Release workflow">
  <TabsTrigger value="design">Design</TabsTrigger>
  <TabsTrigger value="build">Build</TabsTrigger>
  <TabsTrigger value="ship" disabled>Ship</TabsTrigger>
</TabsList>

<TabsContent value="design">Design review is complete.</TabsContent>
<TabsContent value="build">Build is in progress.</TabsContent>
<TabsContent value="ship">Shipping is locked until QA passes.</TabsContent>
</Tabs>

Shape

Use the shape prop on Tabs to change the corners of the tab list and triggers: default (rounded), hard (square), soft (pill) or squircle. A squircle is a rounded square with smooth corners. Browsers without corner-shape support show normal rounded corners. Underline tabs keep square corners with any shape.

<div class="zui-tabs" data-orientation="horizontal">
<div class="zui-tabs-list" role="tablist" aria-label="Default tabs">
  <button class="zui-tabs-trigger" type="button" role="tab" aria-selected="true" data-state="active">Account</button>
  <button class="zui-tabs-trigger" type="button" role="tab" aria-selected="false">Security</button>
</div>
<div class="zui-tabs-content" role="tabpanel">Default</div>
</div>
<div class="zui-tabs zui-tabs-shape-hard" data-orientation="horizontal">
<div class="zui-tabs-list" role="tablist" aria-label="Hard tabs">
  <button class="zui-tabs-trigger" type="button" role="tab" aria-selected="true" data-state="active">Account</button>
  <button class="zui-tabs-trigger" type="button" role="tab" aria-selected="false">Security</button>
</div>
<div class="zui-tabs-content" role="tabpanel">Hard</div>
</div>
<div class="zui-tabs zui-tabs-shape-soft" data-orientation="horizontal">
<div class="zui-tabs-list" role="tablist" aria-label="Soft tabs">
  <button class="zui-tabs-trigger" type="button" role="tab" aria-selected="true" data-state="active">Account</button>
  <button class="zui-tabs-trigger" type="button" role="tab" aria-selected="false">Security</button>
</div>
<div class="zui-tabs-content" role="tabpanel">Soft</div>
</div>
<div class="zui-tabs zui-tabs-shape-squircle" data-orientation="horizontal">
<div class="zui-tabs-list" role="tablist" aria-label="Squircle tabs">
  <button class="zui-tabs-trigger" type="button" role="tab" aria-selected="true" data-state="active">Account</button>
  <button class="zui-tabs-trigger" type="button" role="tab" aria-selected="false">Security</button>
</div>
<div class="zui-tabs-content" role="tabpanel">Squircle</div>
</div>

To make squircle the default everywhere, add zui-squircle-all to <html> — see Squircle.

CSS custom properties

Set these on :root, on a wrapper, or on the element. See Component custom properties.

Property Default Description
--zui-tabs-gap var(--space-xs) Gap between the tab list and the panel
--zui-tabs-list-gap var(--space-4xs) Gap between triggers
--zui-tabs-list-padding var(--space-4xs) Padding inside the tab list
--zui-tabs-list-radius var(--radius-xl) Border radius of the tab list
--zui-tabs-list-border var(--color-border) Border colour of the tab list
--zui-tabs-list-bg color-mix(in oklch, var(--color-text) 4%, var(--color-surface)) Background of the tab list
--zui-tabs-trigger-radius List radius minus list padding Border radius of each trigger
--zui-tabs-trigger-padding-inline var(--space-sm) Horizontal padding inside each trigger
--zui-tabs-trigger-padding-block var(--space-3xs) Vertical padding inside each trigger
--zui-tabs-trigger-color color-mix(in oklch, var(--color-text) 72%, transparent) Trigger text colour
--zui-tabs-trigger-active-bg var(--color-surface) Background of the active trigger
--zui-tabs-trigger-active-color var(--color-text) Text colour of the active trigger
--zui-tabs-content-padding var(--space-xs) 0 Padding of the tab panel

Notes

Theme

Copy this CSS to your project: