Live Preview
Primary — M3 (stacked icons, rounded indicator)
Overview
Analytics
Settings
Primary tab content — stacked icon layout, 3dp rounded indicator.
Secondary — M3 (inline icons, full-width indicator)
All
Income
Expense
Transfers
Secondary tab content — 2dp full-width underline, on-surface text.
Enclosed — bordered container
Code
Preview
Console
Segment — iOS Segmented Control
Day
Week
Month
Year
Tabs Component Generation Skill
Skill này hướng dẫn bạn (AI Agent) tạo component Tabs — điều hướng chuyển đổi nội dung theo nhóm.
1. Mục tiêu (Objective)
Tạo component Tabs hoàn chỉnh gồm TabList + TabPanel, hỗ trợ nhiều visual variants, keyboard navigation, và tích hợp 100% Design Tokens.
2. AI Context & Intent (Ngữ cảnh cho AI)
Khi nào dùng Tabs?
- Chuyển đổi nội dung cùng hierarchy: tab "Overview", "Details", "Reviews"
- Giảm scroll: Chia nội dung dài thành nhóm xem theo tab
- Trong cùng page (không điều hướng URL)
Phân biệt với component khác
| Tình huống | Component đúng | Lý do |
|---|---|---|
| Chuyển nội dung cùng page | Tabs | Client-side switch |
| Điều hướng giữa pages | Navigation / Menu | Route change |
| 2–3 options dạng toggle | Segmented Control | Compact inline |
| Thu gọn nội dung | Accordion | Show/hide, không switch |
| Step-by-step flow | Stepper | Sequential progress |
Decision Tree cho AI
text
Cần chuyển đổi view?
├─ Cùng page, nội dung parallel → Tabs
├─ Khác page (URL change) → Navigation
├─ 2–3 options, inline compact → Segmented Control
├─ Show/hide sections → Accordion
└─ Sequential steps → Stepper / Wizard
3. Ngữ nghĩa & Phân loại (Semantics)
3.1. Visual Variants
| Variant | Mô tả | Active indicator | Icon layout | Ref |
|---|---|---|---|---|
primary |
M3 Primary Tabs — top-level navigation (mặc định) | 3dp rounded-top underline, content-width, inset 2dp | Stacked (trên label) | M3 Tabs |
secondary |
M3 Secondary Tabs — sub-level trong content area | 2dp full-width underline | Inline (trái label) | M3 Tabs |
enclosed |
Tab items trong container, active tab nổi bật | Background base.surface, border |
Inline | — |
segment |
iOS Segmented Control — pill-shaped toggle | Background base.primary-muted, radius full |
Inline | Apple HIG |
Backward compat:
line→ alias chosecondary.chip→ alias chosegment.
Primary vs Secondary — Khi nào dùng?
| Tình huống | Variant |
|---|---|
| Top-level tabs ngay dưới app bar | primary |
| Sub-tabs bên trong 1 content section | secondary |
| Enclosed/bordered group | enclosed |
| iOS segment toggle (2–4 items) | segment |
3.2. Features
- With icons: Icon + text trong tab label
- With badge: Counter badge trên tab (vd: "Comments (5)")
- Scrollable: Khi quá nhiều tabs, horizontal scroll + arrows
- With close: Tab có thể đóng (vd: browser-like tabs)
3.4. Slot Map (Figma ↔ Code)
📎 Source:
slot-manifest.json→tabs· Layer: card
| Figma Slot | data-slot |
CSS Class | Required | Accepts |
|---|---|---|---|---|
| Root | tabs |
.tabs |
✅ | — |
| List | tabs-list |
.tab-list · .slot-header |
✅ | tab-trigger-group |
| Trigger | tabs-trigger |
.tab |
✅ (n×) | text, icon+text |
| Content | tabs-content |
.slot-body |
✅ | * |
4. Token Mapping
📦 Token values: Xem
ATOMIC-MAPPING.md— single source of truth cho tất cả actual token values.
4.1. Shared Tokens (tất cả variants)
| Token | Type | Value | Mô tả |
|---|---|---|---|
tab-padding-x |
number | {item.padding-default} |
|
tab-padding-y |
number | {item.padding-small} |
|
tab-gap |
number | {spacing.1} |
Gap between tab items |
tab-font-size |
number | {font.size.sm} |
|
tab-font-weight-active |
number | {font.weight.semibold} |
|
tab-font-weight-inactive |
number | {font.weight.medium} |
|
panel-padding |
number | {module.padding-default} |
|
color.tab-text-inactive |
color | {base.muted-foreground} |
M3: on-surface-variant |
color.tab-hover |
color | {state-layer.hover} |
|
color.panel-border |
color | {base.border} |
|
color.focus-ring |
color | {focus.ring-color} |
Focus ring — ref shared/focus |
color.disabled-bg |
color | {base.muted} |
|
color.disabled-fg |
color | {base.muted-foreground} |
|
transition |
string | {duration.normal-1} {easing.standard} |
Indicator animation |
4.2. Primary Variant Tokens (M3 Primary Tabs)
| Token | Type | Value | Mô tả |
|---|---|---|---|
tab-height-primary |
number | 48dp |
Label-only height |
tab-height-primary-icon |
number | 64dp |
Icon + label (stacked) height |
indicator-height-primary |
number | 3dp |
Active indicator thickness |
indicator-inset-primary |
number | 2dp |
Horizontal inset each side |
indicator-radius-primary |
number | 3dp |
Top-left + top-right radius |
indicator-min-width-primary |
number | 24dp |
Minimum indicator length |
icon-text-gap-primary |
number | {spacing.1} |
Gap between stacked icon and label |
color.tab-list-bg-primary |
color | transparent |
|
color.tab-text-active-primary |
color | {base.primary} |
M3: primary |
color.indicator-primary |
color | {base.primary} |
M3: primary |
color.tab-divider |
color | {base.border} |
1dp bottom divider |
tab-divider-height |
number | 1dp |
Divider thickness |
4.3. Secondary Variant Tokens (M3 Secondary Tabs)
| Token | Type | Value | Mô tả |
|---|---|---|---|
tab-height-secondary |
number | 48dp |
|
indicator-height-secondary |
number | 2dp |
Active indicator thickness |
color.tab-list-bg-secondary |
color | transparent |
|
color.tab-text-active-secondary |
color | {base.foreground} |
M3: on-surface |
color.indicator-secondary |
color | {base.primary} |
M3: primary |
color.tab-divider |
color | {base.border} |
1dp bottom divider (shared) |
4.4. Enclosed Variant Tokens
| Token | Type | Value | Mô tả |
|---|---|---|---|
radius-enclosed |
number | {item.radius} |
|
color.tab-list-bg-enclosed |
color | {base.muted} |
|
color.active-bg-enclosed |
color | {base.input} |
bg/fg pair: input |
color.active-text-enclosed |
color | {base.input-foreground} |
bg/fg pair: input-foreground |
4.5. Segment Variant Tokens (iOS Segmented, trước đây chip)
| Token | Type | Value | Mô tả |
|---|---|---|---|
radius-segment |
number | {border-radius.full} |
|
color.tab-list-bg-segment |
color | {base.muted} |
|
color.tab-text-active-segment |
color | {base.input-foreground} |
Same as Enclosed, khác shape |
color.active-bg-segment |
color | {base.input} |
White bg like Enclosed |
5. Props & API
typescript
interface TabsProps {
/** Active tab value */
value?: string;
/** Default active tab (uncontrolled) */
defaultValue?: string;
/** Visual variant — default: 'primary' */
variant?: 'primary' | 'secondary' | 'enclosed' | 'segment';
/** Size */
size?: 'sm' | 'md' | 'lg';
/** Orientation */
orientation?: 'horizontal' | 'vertical';
/** Full width (tabs stretch) */
fullWidth?: boolean;
/**
* Icon position relative to label.
* Auto-resolved from variant if omitted:
* primary → 'stacked' (icon above label)
* secondary/enclosed/segment → 'inline' (icon left of label)
*/
iconPosition?: 'stacked' | 'inline';
/** onChange callback */
onChange?: (value: string) => void;
children: ReactNode;
}
interface TabProps {
/** Unique value identifier */
value: string;
/** Label */
label: string;
/** Icon */
icon?: ReactNode;
/** Badge / counter */
badge?: string | number;
/** Disabled */
disabled?: boolean;
/** Closable */
closable?: boolean;
}
interface TabPanelProps {
/** Matching value from Tab */
value: string;
children: ReactNode;
}
6. Accessibility (a11y)
- TabList:
role="tablist",aria-orientation="horizontal"/"vertical". - Tab:
role="tab",aria-selected="true"/"false",aria-controls="[panel-id]". - TabPanel:
role="tabpanel",aria-labelledby="[tab-id]",tabindex="0". - Keyboard:
Arrow Left/Right(horizontal) hoặcArrow Up/Down(vertical) di chuyển giữa tabs.Home→ first tab,End→ last tab. - Focus management: Roving tabindex — chỉ active tab có
tabindex="0", otherstabindex="-1". - Disabled tab:
aria-disabled="true", skip khi keyboard navigate.
7. Best Practices & Rules
- Semantic HTML: Sử dụng đúng ARIA roles, không dùng
<div>thuần. - Lazy loading panels: Chỉ render panel content của active tab (performance).
- Persistent state: Content trong inactive tabs NÊN giữ state khi quay lại.
- Active indicator animation: Smooth transition khi switch tabs.
- Không Hardcode: Mọi giá trị từ Token.
- Responsive: Trên mobile, tabs có thể scroll horizontal hoặc chuyển thành dropdown.
- Icon: Tab icon CHỈ dùng Lucide icon từ
assets/icons/. CẤM dùng text emoji. Xemicon.mdmục 10.
8. Example Usage
jsx
{/* ✅ M3 Primary tabs (default) — stacked icons */}
<Tabs defaultValue="overview" variant="primary">
<TabList>
<Tab value="overview" label="Tổng quan" icon={<LayoutDashboard />} />
<Tab value="transactions" label="Giao dịch" icon={<ArrowLeftRight />} badge={5} />
<Tab value="settings" label="Cài đặt" icon={<Settings />} />
</TabList>
<TabPanel value="overview">Nội dung tổng quan...</TabPanel>
<TabPanel value="transactions">Danh sách giao dịch...</TabPanel>
<TabPanel value="settings">Cài đặt tài khoản...</TabPanel>
</Tabs>
{/* ✅ M3 Secondary tabs — inline icons, sub-level content */}
<Tabs defaultValue="all" variant="secondary">
<TabList>
<Tab value="all" label="Tất cả" />
<Tab value="income" label="Thu nhập" />
<Tab value="expense" label="Chi tiêu" />
</TabList>
</Tabs>
{/* ✅ Segment tabs (iOS Segmented Control) */}
<Tabs variant="segment" size="sm" fullWidth>
<TabList>
<Tab value="day" label="Ngày" />
<Tab value="week" label="Tuần" />
<Tab value="month" label="Tháng" />
</TabList>
</Tabs>
{/* ✅ Enclosed tabs */}
<Tabs variant="enclosed">
<TabList>
<Tab value="code" label="Code" />
<Tab value="preview" label="Preview" />
</TabList>
</Tabs>