Nền tảng
Chuyển động
Đã đặc tảNăm bậc thời lượng, bốn đường cong, sáu cặp presence, và một pressScale ở trên giá — không áp cho primitive nào. prefers-reduced-motion: reduce đưa mọi thời lượng về 0 và mọi presence về none. Có đúng một ngoại lệ trong cả kit: chỉ báo loading của Button.
Token
Thời lượng
| Token | Giá trị |
|---|---|
--nf-foundation-motion-duration-instant | Unknown |
--nf-foundation-motion-duration-fast | Unknown |
--nf-foundation-motion-duration-standard | Unknown |
--nf-foundation-motion-duration-deliberate | Unknown |
--nf-foundation-motion-duration-extended | Unknown |
Thang dừng ở 320ms: trên đó, một chuyển động chuyển từ "phản hồi" sang "chờ", và thứ cần sửa là chuyển động đó, không phải thêm một bậc.
Dialog: deliberate 240ms so với extended 320ms. Clause 15 của DS-014 rút --nf-motion-dialog-in từ extended (320ms) xuống deliberate (240ms) — bản bên trái đang ship hôm nay. Lập luận là số học về đường cong: easing-enter là cubic-bezier(0.16, 1, 0.3, 1), đạt phần lớn quãng đường trong khoảng một phần ba đầu, nên ở 320ms đoạn cuối gần như không còn chuyển động nhìn thấy được. Nhưng phán quyết cuối là thẩm mỹ, không phải số học, và thuộc chủ sở hữu — đây là thứ phải nhìn mới quyết được, nên hai bản chạy cạnh nhau dưới đây, giống hệt mọi mặt trừ thời lượng vào.
Đang ship — deliberate, 240ms
Giá trị thật của --nf-motion-dialog-in sau clause 15 của DS-014.
Bản cũ để so — extended, 320ms
Specimen của trang tài liệu: keyframe nf-dialog-in ghép tay với easing-enter và duration-extended, không phải một token composite nào tiêu thụ.
Token
Đường cong
| Token | Giá trị |
|---|---|
--nf-foundation-motion-easing-standard | Unknown |
--nf-foundation-motion-easing-exit | Unknown |
--nf-foundation-motion-easing-enter | Unknown |
--nf-foundation-motion-easing-exit-snappy | Unknown |
Vào và ra không đối xứng, và đó là cố ý: thứ đang đến chậm dần để mắt bắt được chỗ nó dừng; thứ đang đi nhanh dần để ra khỏi đường nhìn.
Token
pressScale — một token trên giá
DS-018 clause 2 đảo DS-014 clause 16. Câu cũ — "áp cho mọi thứ nhấn được… một luật, không ngoại lệ" — không còn hiệu lực. --nf-foundation-motion-press-scale vẫn là 0.98 và vẫn ở trong corpus token. Nó không được áp cho bất kỳ primitive nào, và chỉ được áp khi chủ sở hữu yêu cầu cho một bề mặt cụ thể. infra/check-control-states.mjs chặn chuỗi press-scale xuất hiện trong packages/ui/src/**.
Vì sao. Ở chiều cao mặc định 36px sau DS-017 clause 3, một phép thu 2% dịch 0,7 pixel — dưới ngưỡng đọc được thành phản hồi, nhưng đủ để trình duyệt lấy mẫu lại chữ trong nhãn và làm nó rung.
Cảnh báo
Trạng thái control không còn thuộc trang này. Hover, pressed, focus và selected nay kể bằng màu bề mặt và ring, và chúng sống ở /foundations/states — A33 cấm kể trạng thái bằng hình học. Ranh giới sau DS-018: trạng thái là control đang được tương tác và không có chuyển động; presence là bề mặt đang xuất hiện hay biến mất, và đó là thứ trang này sở hữu.
Bậc fast 120ms vẫn dùng cho chuyển tiếp màu của trạng thái — dòng fast ở bảng thời lượng phía trên vẫn đúng. Cái biến mất là chuyển động hình học, không phải chuyển tiếp.
Render
Presence — sáu cặp, chạy được
Mười hai token --nf-motion-*, sáu cặp vào / ra. Mỗi ô dưới đây chạy đúng chuyển động -in của cặp đó; bấm "Chạy lại" để xem lại.
overlay
Mờ vào và mờ ra. Không di chuyển
menu
Phóng từ overlay-scale. Ra bằng exit-snappy
popover
Mờ vào, phóng từ overlay-scale, cộng dịch chuyển 4px ngược chiều data-side
dialog
Nghỉ ở giữa màn hình, vào bằng deliberate và enter
sheet
Đi theo trục Y vào hộp nghỉ của nó, sát cạnh
toast
Neo đáy. Vào và ra theo DS-008
Trợ năng
Reduced motion
Khối @media (prefers-reduced-motion: reduce) đặt cả năm duration về 0ms và cả mười hai token presence về none. Khối đó không chạm easing và không chạm pressScale: easing giữ nguyên vì với thời lượng 0 đường cong không còn tác dụng, và pressScale giữ nguyên vì nó không phải chuyển động theo thời gian — nó là một trạng thái tĩnh trong lúc ngón tay còn đè.
Cảnh báo
Ngoại lệ duy nhất của cả kit: --nf-component-button-loading-duration dài ra thành -loading-duration-reduced thay vì về 0ms. Một vòng quay đứng yên đọc như một control bị hỏng, không như bình tĩnh. Không có ngoại lệ thứ hai.
Nghĩa vụ
Obligation
A13
A13 Reduced motion
Dưới prefers-reduced-motion: reduce, mọi transition-duration và animation-duration về instant. Trạng thái cuối vẫn phải đúng — tắt chuyển động không được làm mất trạng thái. Ngoại lệ duy nhất là chỉ báo loading của Button, và nó dài ra chứ không dừng, vì một vòng quay đứng yên truyền đạt "đã treo".
Kiểm. Bật giảm chuyển động ở cấp hệ điều hành, hoặc emulate trong devtools. Mở dialog: nó xuất hiện ngay, đúng vị trí. Bấm một nút loading: chỉ báo vẫn quay, chậm hơn.
A14
A14 Không chớp
Không nội dung nào nhấp nháy quá ba lần trong một giây. WCAG 2.3.1 là tiêu chí mức A và liên quan tới nguy cơ co giật — đây không phải chuyện thẩm mỹ. Lỗi được báo bằng chữ, màu và biểu tượng, không bằng nhấp nháy.
Kiểm. Không có @keyframes nào trong corpus có chu kỳ dưới 333ms đi kèm thay đổi độ sáng lớn.
A15
A15 Một hệ chuyển động
Kit đã có hệ chuyển động. Không thêm Framer Motion, GSAP, react-spring, hay CSS animation viết tay cho việc mà token đã làm được — hai hệ chuyển động nghĩa là hai nơi phải nhớ tắt khi reduced-motion bật, và một trong hai sẽ bị quên.
Kiểm. package.json của mọi app không có phụ thuộc animation. Mọi animation trong mã đọc var(--nf-motion-*).
Token
Alias component
| Token | Trỏ về |
|---|---|
--nf-component-transition-duration | fast |
--nf-component-transition-easing | standard |
--nf-component-overlay-scale | thang phóng của menu và dialog |
--nf-component-button-loading-duration | thời lượng vòng loading; dài ra ở reduced motion |
--nf-component-button-loading-duration-reduced | giá trị dài dùng ở reduced motion |
Quy tắc
Do / Don't
| Do | Don't | Vì sao |
|---|---|---|
| Dùng một cặp presence có sẵn | Ghép duration với easing để tự dựng animation | Hai dialog tự ghép sẽ vào khác nhau |
| Dùng fast cho hover và pressed | Dùng standard cho hover | 180ms trên hover làm con trỏ có cảm giác dính |
| Dùng extended cho thứ chặn người dùng | Dùng extended cho một tooltip | Tooltip chậm đọc như trang bị treo |
| Để reduced motion về 0 | Thêm một hệ chuyển động thứ hai để "vẫn có gì đó" | A15: một hệ chuyển động |
| Đọc --nf-component-transition-* từ primitive | Gõ 120ms | Một ms trần không theo reduced motion |
Chưa quyết
Open questions
| Question | Owner | Resolves when |
|---|---|---|
| Có cặp presence thứ sáu cho popover không, hay popover dùng menu? | — | Đóng 2026-09-24 theo DS-014 clause 14. Cặp thứ sáu của riêng nó |
| pressScale có nên tắt ở reduced motion không? | nexoboxvn@gmail.com | Có phản hồi từ người dùng thật |
| Có cần token cho độ trễ, ví dụ độ trễ hiện tooltip? | nexoboxvn@gmail.com | Tooltip trở thành một primitive trong catalogue |