Accordion
Mẫu chuẩnMột danh sách các mục có thể mở/đóng để xem nội dung bên trong, mở/đóng tức thời.
Playground
Đổi brand, theme, type và trạng thái. Không có prop size, không có prop variant.
Bấm một trigger, hoặc dùng Tab + Enter/Space để mở đóng. Mũi tên Lên/Xuống di chuyển giữa các trigger.
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@nexobox/ui";
<Accordion type="single" collapsible defaultValue="item-1">
<AccordionItem value="item-1">
<AccordionTrigger>Accordion là gì?</AccordionTrigger>
<AccordionContent>Một danh sách các mục có thể mở/đóng để xem nội dung bên trong, dựng trên Radix Accordion.</AccordionContent>
</AccordionItem>
<AccordionItem value="item-2">
<AccordionTrigger>Có animation khi mở không?</AccordionTrigger>
<AccordionContent>Không. Repo chưa có token --nf-motion-accordion-*, nên mở/đóng tức thời.</AccordionContent>
</AccordionItem>
</Accordion>Khi nào sử dụng
Nên dùng
Nội dung dài, phân mảnh thành nhiều phần, mà người đọc chỉ cần xem một phần mỗi lúc: FAQ, các phần tuỳ chọn nâng cao trong một biểu mẫu, chi tiết bổ sung của từng hàng trong danh sách.
Tránh dùng
Các khung nhìn ngang hàng mà người dùng so sánh qua lại — đó là Tabs. Giấu nội dung then chốt mà ai cũng phải đọc. Một khối duy nhất chỉ cần bật/tắt hiển thị — đó là Collapsible, và @nexobox/ui chưa bọc primitive này.
Anatomy
| # | Phần | Ghi chú |
|---|---|---|
| 1 | Accordion | AccordionPrimitive.Root. Giữ state, quyết định type single/multiple. |
| 2 | AccordionItem | Một hàng. Viền dưới --nf-semantic-border-subtle, không nền, không bo góc. |
| 3 | AccordionTrigger | Tự bọc trong AccordionPrimitive.Header — Radix yêu cầu Header là cha trực tiếp của Trigger. Render h3. Cao 48 px ở comfortable, tăng lên 44 px ở compact — chiều duy nhất trong kit đi ngược hướng density, vì trigger là tiêu đề của cả một vùng chứ không phải một mục trong danh sách (DS-017 clause 8). |
| 4 | Chevron | ArrowDown01Icon qua Icon của package, cuối trigger, xoay 180° khi data-state=open, aria-hidden. |
| 5 | AccordionContent | Nội dung mở ra. Không portal — nằm tại chỗ trong cây DOM. |
Variants & states
Không có variant. Không có size. Không có animation khi mở/đóng — repo chưa có token --nf-motion-accordion-*. Loading, error, empty: không có, Accordion không bất đồng bộ.
Trigger hiện, Content không render trong cây trợ năng.
data-state=open trên trigger, chevron xoay 180°, Content hiện.
Di chuyển chuột thật vào trigger để xem hover — nền đổi thành --nf-component-accordion-trigger-hover.
Giữ chuột thật xuống để xem pressed — nền đổi thành --nf-component-accordion-trigger-pressed. Không co scale — DS-018 rút press-scale khỏi mọi primitive.
Outline theo --nf-component-focus-*, offset riêng. Ảnh mô phỏng bằng class xem trước, không phải Tab thật.
Cursor not-allowed, chữ đổi màu --nf-component-button-disabled-foreground.
Design tokens
Giá trị theo NexoCrew / light đang chọn trong playground. Item đọc --nf-semantic-border-subtle. Trigger đọc --nf-component-accordion-trigger-height, -trigger-hover, -trigger-pressed, -trigger-padding-block, -trigger-gap — alias riêng của Accordion, không còn bind thẳng --nf-foundation-size-* — cộng --nf-semantic-text-primary, --nf-component-focus-color, --nf-component-focus-width, --nf-component-focus-offset, --nf-component-button-disabled-foreground. Content đọc --nf-semantic-text-secondary và --nf-component-accordion-content-padding. Vẫn không tồn tại --nf-motion-accordion-* — mở/đóng tức thời, không đổi trong đợt này.
API & triển khai
| Prop | Mặc định | Quy ước |
|---|---|---|
| type | — | Bắt buộc. "single" | "multiple". |
| value | — | Chế độ controlled. string khi single, string[] khi multiple. |
| defaultValue | — | Chế độ uncontrolled. |
| onValueChange | — | Phát khi tập mục mở đổi. |
| collapsible | false | Chỉ có nghĩa khi type="single". Mục đang mở có đóng lại được không. |
| disabled | false | Trên Root: khoá toàn bộ. Trên Item hoặc Trigger: khoá riêng một mục. |
| orientation | "vertical" | Hướng điều hướng bằng phím mũi tên. |
| dir | — | Hướng đọc "ltr" | "rtl". |
Các type trên là ComponentPropsWithoutRef của Radix nên asChild, id, className, style truyền thẳng xuống phần tử tương ứng.