Skip to content
Highlight

V2 componentsUpdated Oct 14, 2026

Tabs

A segmented control: a rail with one thumb that slides to the chosen tab. Tabs are equal width and fill the list, so keep labels short and the count small. Icon tabs size the list to themselves instead.

Source: packages/ui/src/components/tabs

tsx
import { Tabs } from '@highlight/ui'

Change the props; the preview and the snippet update together.

Three decisions, two open questions, one owner assigned.
Props
Size
3
Content
Pending (2nd)
Disabled (last)
Panels
tsx
<Tabs defaultValue="summary">  <Tabs.List>    <Tabs.Tab value="summary">Summary</Tabs.Tab>    <Tabs.Tab value="transcript">Transcript</Tabs.Tab>    <Tabs.Tab value="actions">Actions</Tabs.Tab>  </Tabs.List>  <Tabs.Panel value="summary">    Three decisions, two open questions, one owner assigned.  </Tabs.Panel>  <Tabs.Panel value="transcript">42 minutes, 4 speakers.</Tabs.Panel>  <Tabs.Panel value="actions">Draft the launch checklist by Friday.</Tabs.Panel></Tabs>
Three decisions, two open questions, one owner assigned.
tsx
import { Tabs } from '@highlight/ui' export default function TabsBasic() {  return (    <Tabs defaultValue="summary" className="w-80 gap-3">      <Tabs.List>        <Tabs.Tab value="summary">Summary</Tabs.Tab>        <Tabs.Tab value="transcript">Transcript</Tabs.Tab>        <Tabs.Tab value="actions">Actions</Tabs.Tab>      </Tabs.List>      <Tabs.Panel value="summary" className="text-foreground-secondary text-sm">        Three decisions, two open questions, one owner assigned.      </Tabs.Panel>      <Tabs.Panel value="transcript" className="text-foreground-secondary text-sm">        42 minutes, 4 speakers.      </Tabs.Panel>      <Tabs.Panel value="actions" className="text-foreground-secondary text-sm">        Draft the launch checklist by Friday.      </Tabs.Panel>    </Tabs>  )}

sm (24px) for a dense toolbar, md (28px, the default) for everything else. Type stays 13px at both.

tsx
import { Tabs } from '@highlight/ui' export default function TabsSizes() {  return (    <div className="flex w-80 flex-col gap-4">      <Tabs defaultValue="summary">        <Tabs.List size="sm">          <Tabs.Tab value="summary">Summary</Tabs.Tab>          <Tabs.Tab value="transcript">Transcript</Tabs.Tab>          <Tabs.Tab value="actions">Actions</Tabs.Tab>        </Tabs.List>      </Tabs>      <Tabs defaultValue="summary">        <Tabs.List size="md">          <Tabs.Tab value="summary">Summary</Tabs.Tab>          <Tabs.Tab value="transcript">Transcript</Tabs.Tab>          <Tabs.Tab value="actions">Actions</Tabs.Tab>        </Tabs.List>      </Tabs>    </div>  )}

Text, an icon before the text, or the icon alone. Icon tabs take iconOnly, which drops the padding and sizes the list to its tabs, each at least as wide as the list is tall. Give every icon-only tab an aria-label, since the icon is all that names it.

tsx
import { Tabs } from '@highlight/ui'import { Calendar, Kanban, List } from 'lucide-react' const VIEWS = [  { value: 'list', label: 'List', Icon: List },  { value: 'board', label: 'Board', Icon: Kanban },  { value: 'calendar', label: 'Calendar', Icon: Calendar },] export default function TabsContent() {  return (    <div className="flex w-80 flex-col items-start gap-4">      <Tabs defaultValue="list" className="w-full">        <Tabs.List>          {VIEWS.map(({ value, label }) => (            <Tabs.Tab key={value} value={value}>              {label}            </Tabs.Tab>          ))}        </Tabs.List>      </Tabs>      <Tabs defaultValue="list" className="w-full">        <Tabs.List>          {VIEWS.map(({ value, label, Icon }) => (            <Tabs.Tab key={value} value={value}>              <Icon />              {label}            </Tabs.Tab>          ))}        </Tabs.List>      </Tabs>      <Tabs defaultValue="list">        <Tabs.List iconOnly>          {VIEWS.map(({ value, label, Icon }) => (            <Tabs.Tab key={value} value={value} aria-label={label}>              <Icon />            </Tabs.Tab>          ))}        </Tabs.List>      </Tabs>    </div>  )}

isPending shows a spinner next to the label while that panel loads.

tsx
import { Tabs } from '@highlight/ui' export default function TabsPending() {  return (    <Tabs defaultValue="notes" className="w-80">      <Tabs.List>        <Tabs.Tab value="notes">Notes</Tabs.Tab>        <Tabs.Tab value="summary" isPending>          Summary        </Tabs.Tab>        <Tabs.Tab value="share" disabled>          Share        </Tabs.Tab>      </Tabs.List>    </Tabs>  )}

The root, then any compound sub-components. Every part accepts className and forwards the rest of its props to the element or Base UI part beneath it.

Tabs
Base UI Tabs.Root. Owns the selected value; holds the list and the panels.
Tabs.List
The rail with the sliding thumb. size is sm or md. iconOnly fits the list to icon tabs. indicatorClassName styles the thumb.
Tabs.Tab
One tab; value matches a Panel. isPending adds a spinner.
Tabs.Panel
Content for a value.