Chuyển tới nội dung

Pro đang mở bán trước. Cài đặt mở từ tháng 1/2027. Xem Pro

Component

Tabs

Chuyển giữa các panel nội dung liên quan tại chỗ. Hai kiểu: underline, một thanh dưới tab được chọn trên baseline xuyên hàng, và segmented, một đoạn nổi trong track sunken. Mỗi tab hiện chữ, icon và chữ, hoặc chỉ icon.

  • npm: 2 trực tiếp, 3 tổng
  • registry: 2 trực tiếp, 2 tổng

Xem trước

Tokens, thành phần và tài liệu cho dự án này.

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 min-w-0 max-w-full">
  <TabsList className="h-auto max-w-full flex-wrap gap-y-[max(var(--spacing-xs),calc(var(--size-target-min)-var(--size-element-md)+var(--spacing-xs)+var(--border-width)))]">
    <TabsTrigger value="overview" className="h-[calc(var(--size-element-md)-var(--spacing-xs)-var(--border-width))] min-w-max">Tổng quan</TabsTrigger>
    <TabsTrigger value="activity" className="h-[calc(var(--size-element-md)-var(--spacing-xs)-var(--border-width))] min-w-max">Hoạt động</TabsTrigger>
    <TabsTrigger value="settings" className="h-[calc(var(--size-element-md)-var(--spacing-xs)-var(--border-width))] min-w-max">Cài đặt</TabsTrigger>
  </TabsList>
  <TabsContent value="overview" className="text-sm text-text-secondary">Tokens, thành phần và tài liệu cho dự án này.</TabsContent>
  <TabsContent value="activity" className="text-sm text-text-secondary">Thay đổi từ nhóm của bạn trong 30 ngày qua.</TabsContent>
  <TabsContent value="settings" className="text-sm text-text-secondary">Tên, thành viên và quyền truy cập.</TabsContent>
</Tabs>

Cách dùng

Khi nào dùng

  • Chia một trang hoặc card thành vài phần mà người dùng chuyển tại chỗ: Tổng quan, Hoạt động, Cài đặt.
  • Chuyển giữa các view của cùng nội dung trong chỗ nhỏ, chẳng hạn tệp dạng lưới hoặc danh sách, với kiểu segmented.
  • Một control segmented ngang màn hình điện thoại, mỗi tab một phần bằng nhau, với fullWidth.

Khi nào không dùng

  • Sang trang khác. Dùng link; tab đổi panel tại chỗ.
  • Chạy một hành động, chẳng hạn lưu hoặc xuất. Dùng Button.
  • Chọn một value cho field của form. Dùng Select.
  • Các bước của một luồng phải làm theo thứ tự.

Biến thể

variant

Đặt trên TabsList. underline: các phần của một trang hoặc một card. segmented: chuyển view của cùng nội dung trong chỗ nhỏ, chẳng hạn Lưới và Danh sách, hoặc một bộ lọc cạnh các control khác.

  • Tokens, thành phần và tài liệu cho dự án này.

    underlinemặc định

  • Tokens, thành phần và tài liệu cho dự án này.

    segmented

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

<Tabs defaultValue="overview" className="w-full min-w-0 max-w-full">
  <TabsList className="h-auto max-w-full flex-wrap gap-y-[max(var(--spacing-xs),calc(var(--size-target-min)-var(--size-element-md)+var(--spacing-xs)+var(--border-width)))]">
    <TabsTrigger value="overview" className="h-[calc(var(--size-element-md)-var(--spacing-xs)-var(--border-width))] min-w-max">Tổng quan</TabsTrigger>
    <TabsTrigger value="activity" className="h-[calc(var(--size-element-md)-var(--spacing-xs)-var(--border-width))] min-w-max">Hoạt động</TabsTrigger>
    <TabsTrigger value="settings" className="h-[calc(var(--size-element-md)-var(--spacing-xs)-var(--border-width))] min-w-max">Cài đặt</TabsTrigger>
  </TabsList>
  <TabsContent value="overview" className="text-sm text-text-secondary">Tokens, thành phần và tài liệu cho dự án này.</TabsContent>
  <TabsContent value="activity" className="text-sm text-text-secondary">Thay đổi từ nhóm của bạn trong 30 ngày qua.</TabsContent>
  <TabsContent value="settings" className="text-sm text-text-secondary">Tên, thành viên và quyền truy cập.</TabsContent>
</Tabs>
<Tabs defaultValue="overview" className="w-full min-w-0 max-w-full">
  <TabsList variant="segmented" className="h-auto max-w-full flex-wrap gap-y-[max(var(--spacing-xs),calc(var(--size-target-min)-var(--size-element-md)+2*var(--spacing-xs)+2*var(--border-width)))]">
    <TabsTrigger value="overview" className="h-[calc(var(--size-element-md)-2*var(--spacing-xs)-2*var(--border-width))] min-w-max">Tổng quan</TabsTrigger>
    <TabsTrigger value="activity" className="h-[calc(var(--size-element-md)-2*var(--spacing-xs)-2*var(--border-width))] min-w-max">Hoạt động</TabsTrigger>
    <TabsTrigger value="settings" className="h-[calc(var(--size-element-md)-2*var(--spacing-xs)-2*var(--border-width))] min-w-max">Cài đặt</TabsTrigger>
  </TabsList>
  <TabsContent value="overview" className="text-sm text-text-secondary">Tokens, thành phần và tài liệu cho dự án này.</TabsContent>
  <TabsContent value="activity" className="text-sm text-text-secondary">Thay đổi từ nhóm của bạn trong 30 ngày qua.</TabsContent>
  <TabsContent value="settings" className="text-sm text-text-secondary">Tên, thành viên và quyền truy cập.</TabsContent>
</Tabs>

size

Đặt trên TabsList. Chiều cao list lấy từ size-element-*: sm 28px, md 32px (mặc định), lg 36px, xl 44px với chuột; trên cảm ứng md là 36px, lg 44px và xl 48px. Khớp Button hoặc Input bên cạnh; dùng lg hoặc xl cho các phần chính của một màn hình trên cảm ứng.

  • Tokens, thành phần và tài liệu cho dự án này.

    sm

  • Tokens, thành phần và tài liệu cho dự án này.

    mdmặc định

  • Tokens, thành phần và tài liệu cho dự án này.

    lg

  • Tokens, thành phần và tài liệu cho dự án này.

    xl

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

<Tabs defaultValue="overview" className="w-full min-w-0 max-w-full">
  <TabsList size="sm" className="h-auto max-w-full flex-wrap gap-y-[max(var(--spacing-xs),calc(var(--size-target-min)-var(--size-element-sm)+var(--spacing-xs)+var(--border-width)))]">
    <TabsTrigger value="overview" className="h-[calc(var(--size-element-sm)-var(--spacing-xs)-var(--border-width))] min-w-max">Tổng quan</TabsTrigger>
    <TabsTrigger value="activity" className="h-[calc(var(--size-element-sm)-var(--spacing-xs)-var(--border-width))] min-w-max">Hoạt động</TabsTrigger>
    <TabsTrigger value="settings" className="h-[calc(var(--size-element-sm)-var(--spacing-xs)-var(--border-width))] min-w-max">Cài đặt</TabsTrigger>
  </TabsList>
  <TabsContent value="overview" className="text-sm text-text-secondary">Tokens, thành phần và tài liệu cho dự án này.</TabsContent>
  <TabsContent value="activity" className="text-sm text-text-secondary">Thay đổi từ nhóm của bạn trong 30 ngày qua.</TabsContent>
  <TabsContent value="settings" className="text-sm text-text-secondary">Tên, thành viên và quyền truy cập.</TabsContent>
</Tabs>
<Tabs defaultValue="overview" className="w-full min-w-0 max-w-full">
  <TabsList className="h-auto max-w-full flex-wrap gap-y-[max(var(--spacing-xs),calc(var(--size-target-min)-var(--size-element-md)+var(--spacing-xs)+var(--border-width)))]">
    <TabsTrigger value="overview" className="h-[calc(var(--size-element-md)-var(--spacing-xs)-var(--border-width))] min-w-max">Tổng quan</TabsTrigger>
    <TabsTrigger value="activity" className="h-[calc(var(--size-element-md)-var(--spacing-xs)-var(--border-width))] min-w-max">Hoạt động</TabsTrigger>
    <TabsTrigger value="settings" className="h-[calc(var(--size-element-md)-var(--spacing-xs)-var(--border-width))] min-w-max">Cài đặt</TabsTrigger>
  </TabsList>
  <TabsContent value="overview" className="text-sm text-text-secondary">Tokens, thành phần và tài liệu cho dự án này.</TabsContent>
  <TabsContent value="activity" className="text-sm text-text-secondary">Thay đổi từ nhóm của bạn trong 30 ngày qua.</TabsContent>
  <TabsContent value="settings" className="text-sm text-text-secondary">Tên, thành viên và quyền truy cập.</TabsContent>
</Tabs>
<Tabs defaultValue="overview" className="w-full min-w-0 max-w-full">
  <TabsList size="lg" className="h-auto max-w-full flex-wrap gap-y-[max(var(--spacing-xs),calc(var(--size-target-min)-var(--size-element-lg)+var(--spacing-xs)+var(--border-width)))]">
    <TabsTrigger value="overview" className="h-[calc(var(--size-element-lg)-var(--spacing-xs)-var(--border-width))] min-w-max">Tổng quan</TabsTrigger>
    <TabsTrigger value="activity" className="h-[calc(var(--size-element-lg)-var(--spacing-xs)-var(--border-width))] min-w-max">Hoạt động</TabsTrigger>
    <TabsTrigger value="settings" className="h-[calc(var(--size-element-lg)-var(--spacing-xs)-var(--border-width))] min-w-max">Cài đặt</TabsTrigger>
  </TabsList>
  <TabsContent value="overview" className="text-sm text-text-secondary">Tokens, thành phần và tài liệu cho dự án này.</TabsContent>
  <TabsContent value="activity" className="text-sm text-text-secondary">Thay đổi từ nhóm của bạn trong 30 ngày qua.</TabsContent>
  <TabsContent value="settings" className="text-sm text-text-secondary">Tên, thành viên và quyền truy cập.</TabsContent>
</Tabs>
<Tabs defaultValue="overview" className="w-full min-w-0 max-w-full">
  <TabsList size="xl" className="h-auto max-w-full flex-wrap gap-y-[max(var(--spacing-xs),calc(var(--size-target-min)-var(--size-element-xl)+var(--spacing-xs)+var(--border-width)))]">
    <TabsTrigger value="overview" className="h-[calc(var(--size-element-xl)-var(--spacing-xs)-var(--border-width))] min-w-max">Tổng quan</TabsTrigger>
    <TabsTrigger value="activity" className="h-[calc(var(--size-element-xl)-var(--spacing-xs)-var(--border-width))] min-w-max">Hoạt động</TabsTrigger>
    <TabsTrigger value="settings" className="h-[calc(var(--size-element-xl)-var(--spacing-xs)-var(--border-width))] min-w-max">Cài đặt</TabsTrigger>
  </TabsList>
  <TabsContent value="overview" className="text-sm text-text-secondary">Tokens, thành phần và tài liệu cho dự án này.</TabsContent>
  <TabsContent value="activity" className="text-sm text-text-secondary">Thay đổi từ nhóm của bạn trong 30 ngày qua.</TabsContent>
  <TabsContent value="settings" className="text-sm text-text-secondary">Tên, thành viên và quyền truy cập.</TabsContent>
</Tabs>

Ví dụ

Mặc định

Tab underline cho các phần của một trang.

Tokens, thành phần và tài liệu cho dự án này.
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"

<Tabs defaultValue="overview" className="w-full min-w-0 max-w-full">
  <TabsList className="h-auto max-w-full flex-wrap gap-y-[max(var(--spacing-xs),calc(var(--size-target-min)-var(--size-element-md)+var(--spacing-xs)+var(--border-width)))]">
    <TabsTrigger value="overview" className="h-[calc(var(--size-element-md)-var(--spacing-xs)-var(--border-width))] min-w-max">Tổng quan</TabsTrigger>
    <TabsTrigger value="activity" className="h-[calc(var(--size-element-md)-var(--spacing-xs)-var(--border-width))] min-w-max">Hoạt động</TabsTrigger>
    <TabsTrigger value="settings" className="h-[calc(var(--size-element-md)-var(--spacing-xs)-var(--border-width))] min-w-max">Cài đặt</TabsTrigger>
  </TabsList>
  <TabsContent value="overview" className="text-sm text-text-secondary">Tokens, thành phần và tài liệu cho dự án này.</TabsContent>
  <TabsContent value="activity" className="text-sm text-text-secondary">Thay đổi từ nhóm của bạn trong 30 ngày qua.</TabsContent>
  <TabsContent value="settings" className="text-sm text-text-secondary">Tên, thành viên và quyền truy cập.</TabsContent>
</Tabs>

Có icon

Một icon trước mọi label, không chỉ trên một vài tab.

Tokens, thành phần và tài liệu cho dự án này.
import { IconChartLine, IconHome, IconSettings } from "@tabler/icons-react"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"

<Tabs defaultValue="overview" className="w-full min-w-0 max-w-full">
  <TabsList className="h-auto max-w-full flex-wrap gap-y-[max(var(--spacing-xs),calc(var(--size-target-min)-var(--size-element-md)+var(--spacing-xs)+var(--border-width)))]">
    <TabsTrigger value="overview" className="h-[calc(var(--size-element-md)-var(--spacing-xs)-var(--border-width))] min-w-max">
      <IconHome size={16} />
      Tổng quan
    </TabsTrigger>
    <TabsTrigger value="activity" className="h-[calc(var(--size-element-md)-var(--spacing-xs)-var(--border-width))] min-w-max">
      <IconChartLine size={16} />
      Hoạt động
    </TabsTrigger>
    <TabsTrigger value="settings" className="h-[calc(var(--size-element-md)-var(--spacing-xs)-var(--border-width))] min-w-max">
      <IconSettings size={16} />
      Cài đặt
    </TabsTrigger>
  </TabsList>
  <TabsContent value="overview" className="text-sm text-text-secondary">Tokens, thành phần và tài liệu cho dự án này.</TabsContent>
  <TabsContent value="activity" className="text-sm text-text-secondary">Thay đổi từ nhóm của bạn trong 30 ngày qua.</TabsContent>
  <TabsContent value="settings" className="text-sm text-text-secondary">Tên, thành viên và quyền truy cập.</TabsContent>
</Tabs>

Chỉ icon

Bộ chuyển view kiểu segmented. Mỗi tab đặt tên view trong aria-label.

Tệp ở dạng lưới ảnh thu nhỏ.
import { IconLayoutGrid, IconList } from "@tabler/icons-react"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"

<Tabs defaultValue="grid" className="min-w-0 max-w-full">
  <TabsList variant="segmented" className="h-auto max-w-full flex-wrap gap-y-[max(var(--spacing-xs),calc(var(--size-target-min)-var(--size-element-md)+2*var(--spacing-xs)+2*var(--border-width)))]">
    <TabsTrigger value="grid" iconOnly aria-label="Lưới" className="h-[calc(var(--size-element-md)-2*var(--spacing-xs)-2*var(--border-width))] min-w-max">
      <IconLayoutGrid size={16} />
    </TabsTrigger>
    <TabsTrigger value="list" iconOnly aria-label="Danh sách" className="h-[calc(var(--size-element-md)-2*var(--spacing-xs)-2*var(--border-width))] min-w-max">
      <IconList size={16} />
    </TabsTrigger>
  </TabsList>
  <TabsContent value="grid" className="text-sm text-text-secondary">Tệp ở dạng lưới ảnh thu nhỏ.</TabsContent>
  <TabsContent value="list" className="text-sm text-text-secondary">Tệp ở dạng danh sách với kích thước và ngày.</TabsContent>
</Tabs>

Rộng hết chiều ngang

Tab segmented chia đều chiều rộng của một màn hình hẹp.

Thay đổi trong 7 ngày qua.
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"

<Tabs defaultValue="week" className="w-full min-w-0 max-w-full">
  <TabsList variant="segmented" fullWidth className="h-auto max-w-full flex-wrap gap-y-[max(var(--spacing-xs),calc(var(--size-target-min)-var(--size-element-md)+2*var(--spacing-xs)+2*var(--border-width)))]">
    <TabsTrigger value="day" className="h-[calc(var(--size-element-md)-2*var(--spacing-xs)-2*var(--border-width))] min-w-max">Ngày</TabsTrigger>
    <TabsTrigger value="week" className="h-[calc(var(--size-element-md)-2*var(--spacing-xs)-2*var(--border-width))] min-w-max">Tuần</TabsTrigger>
    <TabsTrigger value="month" className="h-[calc(var(--size-element-md)-2*var(--spacing-xs)-2*var(--border-width))] min-w-max">Tháng</TabsTrigger>
  </TabsList>
  <TabsContent value="day" className="text-sm text-text-secondary">Thay đổi của hôm nay.</TabsContent>
  <TabsContent value="week" className="text-sm text-text-secondary">Thay đổi trong 7 ngày qua.</TabsContent>
  <TabsContent value="month" className="text-sm text-text-secondary">Thay đổi trong 30 ngày qua.</TabsContent>
</Tabs>

Tab vô hiệu

Ở lại trong hàng nhưng không chọn được; phím mũi tên bỏ qua.

Tokens, thành phần và tài liệu cho dự án này.
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"

<Tabs defaultValue="overview" className="w-full min-w-0 max-w-full">
  <TabsList className="h-auto max-w-full flex-wrap gap-y-[max(var(--spacing-xs),calc(var(--size-target-min)-var(--size-element-md)+var(--spacing-xs)+var(--border-width)))]">
    <TabsTrigger value="overview" className="h-[calc(var(--size-element-md)-var(--spacing-xs)-var(--border-width))] min-w-max">Tổng quan</TabsTrigger>
    <TabsTrigger value="activity" className="h-[calc(var(--size-element-md)-var(--spacing-xs)-var(--border-width))] min-w-max">Hoạt động</TabsTrigger>
    <TabsTrigger value="billing" disabled className="h-[calc(var(--size-element-md)-var(--spacing-xs)-var(--border-width))] min-w-max">Thanh toán</TabsTrigger>
  </TabsList>
  <TabsContent value="overview" className="text-sm text-text-secondary">Tokens, thành phần và tài liệu cho dự án này.</TabsContent>
  <TabsContent value="activity" className="text-sm text-text-secondary">Thay đổi từ nhóm của bạn trong 30 ngày qua.</TabsContent>
  <TabsContent value="billing" className="text-sm text-text-secondary">Gói và hóa đơn.</TabsContent>
</Tabs>

Đang tải tab tiếp theo

Người dùng chọn Hoạt động, tab này vẫn đang tải: Tổng quan ở lại trên màn hình, mờ và được đánh dấu busy. Trong app, pending là isPending của transition đổi tab.

Loading activity
Doanh thu tăng 12% trong tháng này.
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"

<Tabs defaultValue="overview" className="w-80 min-w-0 max-w-full">
  <TabsList className="h-auto max-w-full flex-wrap gap-y-[max(var(--spacing-xs),calc(var(--size-target-min)-var(--size-element-md)+var(--spacing-xs)+var(--border-width)))]">
    <TabsTrigger value="overview" className="h-[calc(var(--size-element-md)-var(--spacing-xs)-var(--border-width))] min-w-max">Tổng quan</TabsTrigger>
    <TabsTrigger value="activity" className="h-[calc(var(--size-element-md)-var(--spacing-xs)-var(--border-width))] min-w-max">Hoạt động</TabsTrigger>
  </TabsList>
  <TabsContent value="overview" pending pendingLabel="Loading activity" className="text-sm text-text-secondary">Doanh thu tăng 12% trong tháng này.</TabsContent>
  <TabsContent value="activity" className="text-sm text-text-secondary">3 new comments.</TabsContent>
</Tabs>

Nên và không nên

Nên

  • Dùng underline cho các phần của một trang hoặc một card: Tổng quan, Hoạt động, Cài đặt.
  • Dùng segmented để chuyển giữa các view của cùng nội dung trong chỗ nhỏ, chẳng hạn Lưới và Danh sách.
  • Giữ label trong một hoặc hai từ, sentence case, và ít tab đủ để hàng vừa mà không cần cuộn.
  • Trong một list, cho mọi tab một icon hoặc không tab nào.
  • Dùng iconOnly chỉ cho view có icon tự rõ, chẳng hạn Lưới và Danh sách, và cho mỗi tab một aria-label.
  • Khớp size của list với Button hoặc Input bên cạnh.
  • Dùng fullWidth cho hai đến bốn tab ngắn ngang màn hình điện thoại hoặc một card, để mỗi tab là một đích bằng nhau, dễ nhấn.
  • Khi một panel tải nội dung, chuyển tab trong một transition (useTransition) và truyền isPending cho TabsContent làm pending: panel hiện tại ở lại đến khi panel tiếp theo sẵn sàng. Hiện Skeleton chỉ cho panel chưa từng tải.

Không nên

  • Đừng dùng tab để sang trang khác. Tab đổi panel tại chỗ; dùng link để điều hướng.
  • Đừng dùng tab cho các bước của một luồng phải làm theo thứ tự.
  • Đừng dùng tab để chạy một hành động. Tab chọn một panel; dùng Button cho hành động.
  • Đừng đặt tab bên trong panel của một tab.
  • Đừng dùng fullWidth với nhiều tab hoặc label dài: phần bằng nhau cắt ngắn label dài.
  • Đừng đổi màu qua className. Chọn một variant. Nếu không variant nào phù hợp, mở một issue thay vì ghi đè.
  • Đừng để trống panel hoặc đổi sang spinner khi tab tiếp theo đang tải.

Props

TênKiểuGiá trịMặc địnhMô tả
variantenumunderline | segmentedunderlineĐặt trên TabsList. underline: các phần của một trang hoặc một card. segmented: chuyển view của cùng nội dung trong chỗ nhỏ, chẳng hạn Lưới và Danh sách, hoặc một bộ lọc cạnh các control khác.
sizeenumsm | md | lg | xlmdĐặt trên TabsList. Chiều cao list lấy từ size-element-*: sm 28px, md 32px (mặc định), lg 36px, xl 44px với chuột; trên cảm ứng md là 36px, lg 44px và xl 48px. Khớp Button hoặc Input bên cạnh; dùng lg hoặc xl cho các phần chính của một màn hình trên cảm ứng.
iconOnlyboolean-falseĐặt trên TabsTrigger. Tab vuông chỉ hiện icon. Cần aria-label.
fullWidthboolean-falseĐặt trên TabsList. List lấp container và mỗi tab lấy một phần bằng nhau. Cho control segmented ngang một màn hình hẹp, hoặc một card có tab trải hết chiều rộng.
disabledboolean-falseĐặt trên TabsTrigger. Tab không chọn được, và phím mũi tên bỏ qua nó.
defaultValuestring--Đặt trên Tabs. Value của tab được chọn ở lần render đầu, khi bạn không control nó.
valuestring--Đặt trên Tabs. Value của tab được chọn, khi bạn control nó. Đi cùng onValueChange.
onValueChangefunction--Đặt trên Tabs. Được gọi với value của tab vừa được chọn.
pendingboolean-falseTabsContent: tab người dùng chọn vẫn đang tải. Chuyển tab trong một transition (useTransition) để panel hiện tại ở lại trên màn hình, và truyền isPending: panel mờ xuống opacity-pending và được đánh dấu aria-busy thay vì để trống.
pendingLabelstring-LoadingTabsContent: trình đọc màn hình đọc trong một status polite khi pending.

Slot

TênBắt buộcMô tả
listcóTabsList với một TabsTrigger cho mỗi tab. Mỗi TabsTrigger có một value.
labelkhôngChữ của một tab: một hoặc hai từ, sentence case. Tổng quan, Hoạt động, Cài đặt. Với iconOnly, chữ chuyển sang aria-label.
iconkhôngIcon tùy chọn trước label, hoặc một mình khi iconOnly.
contentcóMột TabsContent cho mỗi tab, cùng value với TabsTrigger của nó.

Cấu tạo

PhầnMô tả
rootTabs. Giữ value được chọn và xếp list phía trên các panel.
listTabsList: hàng các tab. Đặt variant và size cho mọi tab trong đó, và lấy chiều cao size-element-* để thẳng hàng với Button hoặc Input cùng size.
triggerTabsTrigger: một tab. Kéo cao bằng list.
iconIcon Tabler tùy chọn trước label; fill chỉ được phép trên tab được chọn. 16px ở sm, md và lg, 20px ở xl (DESIGN.md 9.1).
indicatorDấu chọn di chuyển của TabsList: một phần tử trượt tới tab được chọn khi lựa chọn đổi (translate, width và height trong duration-medium với ease-standard, chỉ khi người dùng cho phép motion). Underline: thanh accent 2px trên baseline của list. Segmented: đoạn nổi phía sau tab được chọn.
markDấu chọn mà tab được chọn tự vẽ trên ::after của nó cho đến khi list đo xong các tab (lần vẽ đầu, hoặc trước khi script chạy): thanh accent (underline) hoặc đoạn nổi (segmented). Ẩn khi indicator đã được đặt.
contentTabsContent: panel của một tab. Chỉ tab được chọn được render. Panel mới hiện fade vào trong duration-fast (ngay lập tức khi reduced motion). Khi pending, panel ở lại trên màn hình, mờ xuống opacity-pending (duration-medium) và được đánh dấu aria-busy, với một status polite bên cạnh.
hitArea::before trong suốt, căn giữa mỗi tab, không bao giờ nhỏ hơn size-target-min (24px trên chuột, 44px trên cảm ứng), để tab nhỏ vẫn là đích chạm 44px.

Khả năng truy cập

Role: tablist

PhímHành động
TabChuyển focus vào list tab, tới tab được chọn. Tab lần nữa chuyển focus tới panel được chọn.
ArrowRight, ArrowLeftChuyển focus tới tab tiếp theo hoặc tab trước và chọn tab đó. Vòng từ tab cuối về tab đầu và ngược lại.
Home, EndChuyển focus tới tab đầu hoặc tab cuối và chọn tab đó.
  • TabsList là tablist, mỗi TabsTrigger là button với role tab, aria-selected và aria-controls, mỗi TabsContent là tabpanel được tab của nó đặt tên.
  • Tab được chọn đi theo focus, nên phím mũi tên hiện mỗi panel ngay. Với panel tải chậm, truyền activationMode="manual" trên Tabs: phím mũi tên chỉ chuyển focus, và Enter hoặc Space mới chọn.
  • Tab chỉ có icon không có chữ nhìn thấy, nên cần aria-label. TypeScript bắt buộc aria-label khi iconOnly được đặt.
  • Tab disabled dùng thuộc tính disabled gốc: không chọn được, và phím mũi tên bỏ qua.
  • Lựa chọn không bao giờ chỉ hiện bằng màu: underline thêm thanh, segmented thêm đoạn nổi, trên màu chữ đậm hơn.
  • Panel focus được, nên người dùng bàn phím tới được chữ của panel ngay cả khi panel không có control. Panel hiện focus ring.

Cài đặt

npx werewolf-ui add tabs

Chạy npx werewolf-ui init một lần trong app trước khi thêm component. Init cài token, thêm CSS import và luật cho agent. Add cài component này cùng các dependency và giữ nguyên theme của bạn. Xem Cài đặt để biết yêu cầu và cách cài qua registry.