Chuyển tới nội dung
Component

Tabs

Switches between panels of related content in place. Two looks: underline, a bar under the selected tab on a baseline across the row, and segmented, a raised segment in a sunken track. Each tab shows text, an icon and text, or only an icon.

Xem trước

Tokens, components and docs for this project.

Props

  • variant
  • size
  • iconOnly
  • fullWidth
  • disabled
  • defaultValue
  • value
  • pending
  • pendingLabel
  • icon
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"

<Tabs defaultValue="overview" className="w-full max-w-md">
  <TabsList>
    <TabsTrigger value="overview">Overview</TabsTrigger>
    <TabsTrigger value="activity">Activity</TabsTrigger>
    <TabsTrigger value="settings">Settings</TabsTrigger>
  </TabsList>
  <TabsContent value="overview" className="text-sm text-text-secondary">Tokens, components and docs for this project.</TabsContent>
  <TabsContent value="activity" className="text-sm text-text-secondary">Changes from your team in the last 30 days.</TabsContent>
  <TabsContent value="settings" className="text-sm text-text-secondary">Name, members and access.</TabsContent>
</Tabs>

Cách dùng

Khi nào dùng

  • Split one page or card into a few sections the user switches between in place: Overview, Activity, Settings.
  • Switch between views of the same content in a small space, such as files as a grid or a list, with the segmented look.
  • A segmented control across a phone screen, every tab an equal share, with fullWidth.

Khi nào không dùng

  • Going to another page. Use a link; tabs swap panels in place.
  • Running an action, such as saving or exporting. Use Button.
  • Picking a value for a form field. Use Select.
  • The steps of a flow that must be done in order.

Biến thể

variant

Set on TabsList. underline: sections of a page or a card. segmented: switching views of the same content in a small space, such as Grid and List, or a filter next to other controls.

  • Tokens, components and docs for this project.

    underlinemặc định

  • Tokens, components and docs for this project.

    segmented

import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"

<Tabs defaultValue="overview" className="w-full max-w-md">
  <TabsList>
    <TabsTrigger value="overview">Overview</TabsTrigger>
    <TabsTrigger value="activity">Activity</TabsTrigger>
    <TabsTrigger value="settings">Settings</TabsTrigger>
  </TabsList>
  <TabsContent value="overview" className="text-sm text-text-secondary">Tokens, components and docs for this project.</TabsContent>
  <TabsContent value="activity" className="text-sm text-text-secondary">Changes from your team in the last 30 days.</TabsContent>
  <TabsContent value="settings" className="text-sm text-text-secondary">Name, members and access.</TabsContent>
</Tabs>
<Tabs defaultValue="overview" className="w-full max-w-md">
  <TabsList variant="segmented">
    <TabsTrigger value="overview">Overview</TabsTrigger>
    <TabsTrigger value="activity">Activity</TabsTrigger>
    <TabsTrigger value="settings">Settings</TabsTrigger>
  </TabsList>
  <TabsContent value="overview" className="text-sm text-text-secondary">Tokens, components and docs for this project.</TabsContent>
  <TabsContent value="activity" className="text-sm text-text-secondary">Changes from your team in the last 30 days.</TabsContent>
  <TabsContent value="settings" className="text-sm text-text-secondary">Name, members and access.</TabsContent>
</Tabs>

size

Set on TabsList. List height from size-element-*: sm 28px, md 32px (the default), lg 36px, xl 44px with a mouse; on touch md is 36px, lg 44px and xl 48px. Match the Button or Input next to it; use lg or xl for the main sections of a screen on touch.

  • Tokens, components and docs for this project.

    sm

  • Tokens, components and docs for this project.

    mdmặc định

  • Tokens, components and docs for this project.

    lg

  • Tokens, components and docs for this project.

    xl

import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"

<Tabs defaultValue="overview" className="w-full max-w-md">
  <TabsList size="sm">
    <TabsTrigger value="overview">Overview</TabsTrigger>
    <TabsTrigger value="activity">Activity</TabsTrigger>
    <TabsTrigger value="settings">Settings</TabsTrigger>
  </TabsList>
  <TabsContent value="overview" className="text-sm text-text-secondary">Tokens, components and docs for this project.</TabsContent>
  <TabsContent value="activity" className="text-sm text-text-secondary">Changes from your team in the last 30 days.</TabsContent>
  <TabsContent value="settings" className="text-sm text-text-secondary">Name, members and access.</TabsContent>
</Tabs>
<Tabs defaultValue="overview" className="w-full max-w-md">
  <TabsList>
    <TabsTrigger value="overview">Overview</TabsTrigger>
    <TabsTrigger value="activity">Activity</TabsTrigger>
    <TabsTrigger value="settings">Settings</TabsTrigger>
  </TabsList>
  <TabsContent value="overview" className="text-sm text-text-secondary">Tokens, components and docs for this project.</TabsContent>
  <TabsContent value="activity" className="text-sm text-text-secondary">Changes from your team in the last 30 days.</TabsContent>
  <TabsContent value="settings" className="text-sm text-text-secondary">Name, members and access.</TabsContent>
</Tabs>
<Tabs defaultValue="overview" className="w-full max-w-md">
  <TabsList size="lg">
    <TabsTrigger value="overview">Overview</TabsTrigger>
    <TabsTrigger value="activity">Activity</TabsTrigger>
    <TabsTrigger value="settings">Settings</TabsTrigger>
  </TabsList>
  <TabsContent value="overview" className="text-sm text-text-secondary">Tokens, components and docs for this project.</TabsContent>
  <TabsContent value="activity" className="text-sm text-text-secondary">Changes from your team in the last 30 days.</TabsContent>
  <TabsContent value="settings" className="text-sm text-text-secondary">Name, members and access.</TabsContent>
</Tabs>
<Tabs defaultValue="overview" className="w-full max-w-md">
  <TabsList size="xl">
    <TabsTrigger value="overview">Overview</TabsTrigger>
    <TabsTrigger value="activity">Activity</TabsTrigger>
    <TabsTrigger value="settings">Settings</TabsTrigger>
  </TabsList>
  <TabsContent value="overview" className="text-sm text-text-secondary">Tokens, components and docs for this project.</TabsContent>
  <TabsContent value="activity" className="text-sm text-text-secondary">Changes from your team in the last 30 days.</TabsContent>
  <TabsContent value="settings" className="text-sm text-text-secondary">Name, members and access.</TabsContent>
</Tabs>

Ví dụ

Default

Underline tabs for the sections of a page.

Tokens, components and docs for this project.
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"

<Tabs defaultValue="overview" className="w-full max-w-md">
  <TabsList>
    <TabsTrigger value="overview">Overview</TabsTrigger>
    <TabsTrigger value="activity">Activity</TabsTrigger>
    <TabsTrigger value="settings">Settings</TabsTrigger>
  </TabsList>
  <TabsContent value="overview" className="text-sm text-text-secondary">Tokens, components and docs for this project.</TabsContent>
  <TabsContent value="activity" className="text-sm text-text-secondary">Changes from your team in the last 30 days.</TabsContent>
  <TabsContent value="settings" className="text-sm text-text-secondary">Name, members and access.</TabsContent>
</Tabs>

With icons

An icon before every label, never on only some tabs.

Tokens, components and docs for this project.
import { IconChartLine, IconHome, IconSettings } from "@tabler/icons-react"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"

<Tabs defaultValue="overview" className="w-full max-w-md">
  <TabsList>
    <TabsTrigger value="overview">
      <IconHome size={16} />
      Overview
    </TabsTrigger>
    <TabsTrigger value="activity">
      <IconChartLine size={16} />
      Activity
    </TabsTrigger>
    <TabsTrigger value="settings">
      <IconSettings size={16} />
      Settings
    </TabsTrigger>
  </TabsList>
  <TabsContent value="overview" className="text-sm text-text-secondary">Tokens, components and docs for this project.</TabsContent>
  <TabsContent value="activity" className="text-sm text-text-secondary">Changes from your team in the last 30 days.</TabsContent>
  <TabsContent value="settings" className="text-sm text-text-secondary">Name, members and access.</TabsContent>
</Tabs>

Icon only

A segmented view switcher. Each tab names its view in aria-label.

Files as a grid of thumbnails.
import { IconLayoutGrid, IconList } from "@tabler/icons-react"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"

<Tabs defaultValue="grid">
  <TabsList variant="segmented">
    <TabsTrigger value="grid" iconOnly aria-label="Grid">
      <IconLayoutGrid size={16} />
    </TabsTrigger>
    <TabsTrigger value="list" iconOnly aria-label="List">
      <IconList size={16} />
    </TabsTrigger>
  </TabsList>
  <TabsContent value="grid" className="text-sm text-text-secondary">Files as a grid of thumbnails.</TabsContent>
  <TabsContent value="list" className="text-sm text-text-secondary">Files as a list with size and date.</TabsContent>
</Tabs>

Full width

Segmented tabs that share the width of a narrow screen equally.

Changes from the last 7 days.
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"

<Tabs defaultValue="week" className="w-full max-w-sm">
  <TabsList variant="segmented" fullWidth>
    <TabsTrigger value="day">Day</TabsTrigger>
    <TabsTrigger value="week">Week</TabsTrigger>
    <TabsTrigger value="month">Month</TabsTrigger>
  </TabsList>
  <TabsContent value="day" className="text-sm text-text-secondary">Changes from today.</TabsContent>
  <TabsContent value="week" className="text-sm text-text-secondary">Changes from the last 7 days.</TabsContent>
  <TabsContent value="month" className="text-sm text-text-secondary">Changes from the last 30 days.</TabsContent>
</Tabs>

Disabled tab

Stays in the row but can't be selected; arrow keys skip it.

Tokens, components and docs for this project.
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"

<Tabs defaultValue="overview" className="w-full max-w-md">
  <TabsList>
    <TabsTrigger value="overview">Overview</TabsTrigger>
    <TabsTrigger value="activity">Activity</TabsTrigger>
    <TabsTrigger value="billing" disabled>Billing</TabsTrigger>
  </TabsList>
  <TabsContent value="overview" className="text-sm text-text-secondary">Tokens, components and docs for this project.</TabsContent>
  <TabsContent value="activity" className="text-sm text-text-secondary">Changes from your team in the last 30 days.</TabsContent>
  <TabsContent value="billing" className="text-sm text-text-secondary">Plan and invoices.</TabsContent>
</Tabs>

Next tab loading

The user picked Activity, which is still loading: Overview stays on screen, dimmed and marked busy. In an app, pending is the isPending of the transition that changes the tab.

Loading activity
Revenue is up 12% this month.
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"

<Tabs defaultValue="overview" className="w-80">
  <TabsList>
    <TabsTrigger value="overview">Overview</TabsTrigger>
    <TabsTrigger value="activity">Activity</TabsTrigger>
  </TabsList>
  <TabsContent value="overview" pending pendingLabel="Loading activity" className="text-sm text-text-secondary">Revenue is up 12% this month.</TabsContent>
  <TabsContent value="activity" className="text-sm text-text-secondary">3 new comments.</TabsContent>
</Tabs>

Nên và không nên

Nên

  • Use underline for the sections of a page or a card: Overview, Activity, Settings.
  • Use segmented to switch between views of the same content in a small space, such as Grid and List.
  • Keep labels to one or two words in sentence case, and few enough tabs that the row fits without scrolling.
  • In one list, give every tab an icon or none of them.
  • Use iconOnly only for views whose icon is clear on its own, such as Grid and List, and give each one an aria-label.
  • Match the list size to the Button or Input next to it.
  • Use fullWidth for two to four short tabs across a phone screen or a card, so every tab is an equal, easy target.
  • When a panel loads its content, switch tabs in a transition (useTransition) and pass isPending to TabsContent as pending: the current panel stays until the next one is ready. Show a Skeleton only for a panel that has never loaded.

Không nên

  • Don't use tabs to go to another page. Tabs swap panels in place; use links to navigate.
  • Don't use tabs for the steps of a flow that must be done in order.
  • Don't use tabs to run an action. A tab selects a panel; use Button for actions.
  • Don't put tabs inside a tab panel.
  • Don't use fullWidth with many tabs or long labels: equal shares cut long labels short.
  • Don't restyle colors through className. Pick a variant. If none fits, open an issue instead of overriding it.
  • Don't blank the panel or swap in a spinner while the next tab loads.

Props

TênKiểuGiá trịMặc địnhMô tả
variantenumunderline | segmentedunderlineSet on TabsList. underline: sections of a page or a card. segmented: switching views of the same content in a small space, such as Grid and List, or a filter next to other controls.
sizeenumsm | md | lg | xlmdSet on TabsList. List height from size-element-*: sm 28px, md 32px (the default), lg 36px, xl 44px with a mouse; on touch md is 36px, lg 44px and xl 48px. Match the Button or Input next to it; use lg or xl for the main sections of a screen on touch.
iconOnlyboolean-falseSet on TabsTrigger. A square tab that shows only its icon. Needs aria-label.
fullWidthboolean-falseSet on TabsList. The list fills its container and every tab takes an equal share. For a segmented control across a narrow screen, or a card whose tabs span its width.
disabledboolean-falseSet on TabsTrigger. The tab can't be selected, and arrow keys skip it.
defaultValuestring--Set on Tabs. The value of the tab selected on first render, when you don't control it.
valuestring--Set on Tabs. The value of the selected tab, when you control it. Pair it with onValueChange.
onValueChangefunction--Set on Tabs. Called with the value of the newly selected tab.
pendingboolean-falseTabsContent: the tab the user picked is still loading. Switch tabs in a transition (useTransition) so the current panel stays on screen, and pass its isPending: the panel dims to opacity-pending and is marked aria-busy instead of going blank.
pendingLabelstring-LoadingTabsContent: read by screen readers in a polite status while pending.

Slot

TênBắt buộcMô tả
listcóTabsList with one TabsTrigger per tab. Each TabsTrigger has a value.
labelkhôngThe text of a tab: one or two words, sentence case. Overview, Activity, Settings. With iconOnly it moves to aria-label.
iconkhôngOptional icon before the label, or alone with iconOnly.
contentcóOne TabsContent per tab, with the same value as its TabsTrigger.

Cấu tạo

PhầnMô tả
rootTabs. Holds the selected value and stacks the list above the panels.
listTabsList: the row of tabs. Sets variant and size for every tab in it, and takes the size-element-* height so it lines up with a Button or Input of the same size.
triggerTabsTrigger: one tab. Stretches to the height of the list.
iconOptional Tabler icon before the label; fill is allowed on the selected tab only. 16px in sm, md and lg, 20px in xl (DESIGN.md 9.1).
underlineThe selection bar of the underline variant: an ::after on the selected tab, laid over the list's baseline.
contentTabsContent: the panel of one tab. Only the selected one is rendered. With pending it stays on screen, dims to opacity-pending (duration-medium) and is marked aria-busy, with a polite status next to it.
hitAreaTransparent ::before centered on each tab, never smaller than size-target-min (24px on mouse, 44px on touch), so small tabs stay a 44px touch target.

Khả năng truy cập

Role: tablist

PhímHành động
TabMoves focus into the tab list, onto the selected tab. Tab again moves focus to the selected panel.
ArrowRight, ArrowLeftMoves focus to the next or previous tab and selects it. Wraps from the last tab to the first and back.
Home, EndMoves focus to the first or last tab and selects it.
  • TabsList is the tablist, each TabsTrigger a button with role tab, aria-selected and aria-controls, each TabsContent a tabpanel labelled by its tab.
  • The selected tab follows focus, so arrow keys show each panel right away. For panels that load slowly, pass activationMode="manual" on Tabs: arrow keys only move focus, and Enter or Space selects.
  • An icon-only tab has no visible text, so it needs an aria-label. TypeScript requires one when iconOnly is set.
  • A disabled tab uses the native disabled attribute: it can't be selected, and arrow keys skip it.
  • Selection is never shown by color alone: underline adds the bar, segmented the raised segment, on top of the stronger text color.
  • The panel is focusable, so keyboard users can reach its text even when it holds no control. It shows the focus ring.

Cài đặt

npx shadcn add https://werewolf-ui.vercel.app/r/tabs.json

Lệnh này thêm luôn styles/werewolf.css, bộ token mà mọi component dùng, và các component khác mà nó cần. Import file đó một lần trong CSS gốc, ngay sau @import "tailwindcss", với đường dẫn tính từ file CSS đó (từ app/globals.css là @import "../styles/werewolf.css").