Design Token & Nền tảng thị giácDesign Tokens & Visual Foundations
Thuộc bộ kiến thức Design System Roadmap.
Tổng quan
Design token là đơn vị nhỏ nhất, có tên, của một quyết định thiết kế: một màu sắc, một giá trị spacing, một font size, một border radius, một animation duration. Thay vì developer gõ tay #3B82F6 vào stylesheet và designer chọn cùng màu xanh đó bằng mắt từ swatch, cả hai cùng tham chiếu một token duy nhất — color-primary — được định nghĩa một lần và được dùng ở mọi nơi: CSS trên web, UIColor trên iOS, colorResource trên Android, style trong Figma.
Lợi ích ở đây không phải là thẩm mỹ, mà là vận hành. Nếu không có token, một lần rebrand hay yêu cầu “hỗ trợ dark mode” đồng nghĩa với việc grep tìm mã hex trên hàng trăm file, trong ba codebase khác nhau, và hy vọng không bỏ sót chỗ nào. Có token, cùng một thay đổi đó chỉ là sửa một file JSON (hoặc một tập CSS custom properties) rồi build lại — mọi platform tự động nhận giá trị mới, vì không có nơi nào ở downstream hardcode giá trị thô. Token chính là thứ cho phép design system tách biệt quyết định (đây là màu xanh thương hiệu của chúng ta) khỏi mọi nơi quyết định đó được áp dụng (button, link, focus ring, biểu đồ).
Bài này trình bày cách token được cấu trúc theo tầng (tier), các định dạng file dùng để định nghĩa và phân phối token (CSS custom properties, định dạng JSON của W3C Design Tokens, pipeline build bằng Style Dictionary), và cách các nền tảng thị giác chính của một design system — màu sắc, typography, spacing, grid, breakpoint, icon — tự chúng được biểu diễn dưới dạng token thay vì các giá trị rời rạc gắn cứng trong component. Bài này song hành với Design Language & Nguyên tắc thiết kế (lý do đứng sau các giá trị này), là đầu vào cho Component cốt lõi — Input & Hành động (component tiêu thụ token ở tier component), và được vận hành hoá bởi các pipeline mô tả trong Tooling & Quy trình Design-to-Dev.
Kiến thức nền tảng
Điều gì biến một giá trị thành token
Một giá trị thô (16px, #2563EB) trở thành design token khi có:
- Một cái tên mô tả vai trò hoặc vị trí trong một scale (
spacing-4,blue-600,color-text-danger) thay vì nội dung thực tế của nó. - Một nguồn sự thật duy nhất — một file hoặc hệ thống nơi nó được định nghĩa, từ đó mọi biểu diễn theo từng platform được sinh ra hoặc tham chiếu tới.
- Ý nghĩa độc lập với platform — token
spacing-4nghĩa là “16px trên web, 16pt trên iOS, 16dp trên Android,” cùng một ý định thiết kế được diễn đạt bằng đơn vị gốc của mỗi platform. - Khả năng truy vết — bạn có thể chỉ vào bất kỳ pixel nào trên sản phẩm và nói nó được sinh ra từ token nào, và ngược lại, thay đổi một token cho phép dự đoán mọi nơi bị ảnh hưởng.
Một giá trị đơn lẻ như margin-top: 13px rải rác trong CSS của một component không phải là token — đó chính xác là kiểu quyết định không tài liệu hoá, không tái sử dụng được mà token sinh ra để loại bỏ.
Ba tier: global, semantic, component
Design token gần như luôn được tổ chức theo cấu trúc phân tầng, thường gồm ba tier. Mỗi tier tham chiếu tới tier bên dưới nó, nên thay đổi được lan truyền xuống dưới mà không bao giờ phải sửa giá trị thô hai lần.
Tier 1 — Global / reference token. Bảng màu thô: mọi màu sắc, mọi bước spacing, mọi font size mà hệ thống định nghĩa, được đặt tên theo nó là gì, không phải nó dùng để làm gì. Các token này không mang ý kiến gì về cách sử dụng.
/* Tier 1: global/reference token — bảng màu thô, đặt tên theo mô tả */
:root {
--blue-50: #eff6ff;
--blue-100: #dbeafe;
--blue-500: #3b82f6;
--blue-600: #2563eb;
--blue-700: #1d4ed8;
--red-500: #ef4444;
--red-600: #dc2626;
--gray-50: #f9fafb;
--gray-900: #111827;
}
Tier 2 — Semantic / alias token. Tham chiếu tới token tier 1 nhưng được đặt tên theo ý định hoặc vai trò: color-primary, color-danger, color-surface. Đây là tier thay đổi khi bạn re-theme — đổi color-primary trỏ tới đâu, và mọi consumer đi theo chuỗi này tự cập nhật mà không cần sửa một component nào.
/* Tier 2: semantic/alias token — đặt tên theo vai trò, trỏ tới tier 1 */
:root {
--color-primary: var(--blue-600);
--color-primary-hover: var(--blue-700);
--color-primary-subtle: var(--blue-50);
--color-danger: var(--red-600);
--color-surface: #ffffff;
--color-text: var(--gray-900);
}
Tier 3 — Component token. Tham chiếu tới token tier 2 (đôi khi tier 1) nhưng được giới hạn phạm vi cho các thuộc tính cụ thể của một component. Đây là tier mà một component library thực sự tiêu thụ trong stylesheet của nó.
/* Tier 3: component token — giới hạn cho một component và thuộc tính cụ thể */
:root {
--button-background: var(--color-primary);
--button-background-hover: var(--color-primary-hover);
--button-text-color: #ffffff;
--button-border-radius: var(--radius-md);
--input-border-color: var(--gray-900);
--input-border-color-error: var(--color-danger);
}
Chuỗi blue-600 → color-primary → button-background chính là toàn bộ ý nghĩa của việc chia tier. Rebrand từ xanh sang tím: chỉ sửa color-primary đang alias tới đâu ở tier 2, và button, mọi link, mọi focus ring ở downstream tự cập nhật — không ai đụng vào button.css. Cần một component lệch khỏi theme vì lý do riêng lẻ: chỉ sửa token ở tier 3, và phạm vi ảnh hưởng chỉ giới hạn trong component đó.
Các định dạng token
CSS custom properties là cách đơn giản và được hỗ trợ rộng rãi nhất để phân phối token cho web. Chúng cascade được, có thể override theo từng scope (theo theme, theo component), và trình duyệt đọc trực tiếp được mà không cần build step:
:root {
--color-primary: #2563eb;
--spacing-4: 1rem;
--font-size-body: 1rem;
--radius-md: 0.5rem;
}
.button {
background: var(--color-primary);
padding: var(--spacing-4);
border-radius: var(--radius-md);
}
Giới hạn của chúng: CSS custom properties là một định dạng runtime chỉ dành cho một platform (web). Chúng không mô tả tự nhiên một color asset trên iOS hay một dimension resource trên Android, và không mang metadata (mô tả, type, trạng thái deprecated). Với một design system đa platform, các team thường duy trì một nguồn sự thật độc lập với platform và sinh ra CSS custom properties (cùng với định dạng iOS/Android/Figma) từ đó như một build output.
Định dạng W3C Design Tokens Community Group (DTCG) là chuẩn đang nổi lên cho nguồn sự thật đó: một file JSON trong đó mỗi token có $value, $type, và $description tuỳ chọn, các group được lồng vào nhau để biểu diễn tier:
{
"color": {
"blue": {
"500": { "$value": "#3b82f6", "$type": "color" },
"600": { "$value": "#2563eb", "$type": "color" }
}
},
"semantic": {
"color": {
"primary": {
"$value": "{color.blue.600}",
"$type": "color",
"$description": "Màu action mặc định của thương hiệu; dùng cho primary button, link và focus ring."
},
"primary-hover": {
"$value": "{color.blue.700}",
"$type": "color"
}
}
},
"component": {
"button": {
"background": {
"$value": "{semantic.color.primary}",
"$type": "color"
}
}
},
"spacing": {
"4": { "$value": "16px", "$type": "dimension" }
}
}
Cú pháp {color.blue.600} là một token reference — chính xác quan hệ tier-2-trỏ-tới-tier-1 trong ví dụ CSS ở trên, nhưng được diễn đạt bằng một định dạng mà bất kỳ build tool nào cũng có thể parse và resolve, bất kể platform đích.
Build pipeline: biến một nguồn thành nhiều output
Style Dictionary (ban đầu được xây dựng tại Amazon, nay được dùng rộng rãi) là công cụ tiêu chuẩn de facto cho việc chuyển đổi này: bạn định nghĩa token một lần (bằng DTCG JSON, YAML, hoặc định dạng JSON5 riêng của nó) và cấu hình các “platform,” mỗi platform có một tập transform và một file format, sau đó nó sinh ra output cho từng platform:
// style-dictionary.config.js
module.exports = {
source: ['tokens/**/*.json'],
platforms: {
css: {
transformGroup: 'css',
buildPath: 'build/css/',
files: [{ destination: 'variables.css', format: 'css/variables' }]
},
ios: {
transformGroup: 'ios',
buildPath: 'build/ios/',
files: [{ destination: 'Tokens.swift', format: 'ios-swift/class.swift' }]
},
android: {
transformGroup: 'android',
buildPath: 'build/android/',
files: [{ destination: 'tokens.xml', format: 'android/resources' }]
}
}
};
Chạy style-dictionary build với nguồn token như file DTCG JSON ở trên sẽ sinh ra, cùng nhiều thứ khác, một variables.css với --color-blue-600: #2563eb;, một Tokens.swift với static let colorBlue600 = UIColor(...), và một android/tokens.xml với <color name="color_blue_600">#2563EB</color> — ba artifact platform-native từ một nguồn duy nhất, với các transform xử lý chuyển đổi đơn vị (px → pt, hex → object màu của platform) trong quá trình đó. Đây chính là điều biến “định nghĩa một lần, dùng ở mọi nơi” thành sự thật trong thực tế thay vì chỉ là mong muốn: không có con người nào phải gõ lại mã hex cho team Android.
Khái niệm chính
Màu sắc: color scale, functional color và dark mode
Một hệ thống màu ở mức production hiếm khi là một danh sách màu phẳng — nó được xây từ các scale (ramp), thường 9–10 bước cho mỗi hue (50, 100, 200, ... 900), trong đó 50 là sắc thái gần trắng nhất và 900 là sắc thái gần đen nhất. Đây là cấu trúc mà cả Tailwind CSS và Material Design đều dùng, vì nó giải quyết một vấn đề thật: với một hue thương hiệu, bạn cần các biến thể sáng hơn và tối hơn cho hover state, background nhẹ, border, và trạng thái disabled, và một scale cho bạn một tập hợp có thể dự đoán được, chuyển tiếp có cảm nhận thị giác thay vì các giá trị rời rạc tuỳ tiện.
| Bước | Cách dùng tiêu biểu |
|---|---|
| 50–100 | Background nhẹ, highlight hàng được chọn |
| 200–300 | Border, divider trên bề mặt có màu |
| 400–500 | Icon, nhấn mạnh thứ cấp |
| 600 | Màu interactive mặc định (button, link) |
| 700 | Trạng thái hover / active |
| 800–900 | Text-on-light, điểm nhấn có mức nhấn mạnh cao |
Trên các scale là các màu functional (semantic) — success, warning, danger, info — là một trường hợp đặc biệt của tier semantic: mỗi màu ánh xạ tới một scale (thường là một hue hoàn toàn khác, ví dụ success → green-600, danger → red-600) để ý nghĩa được tách biệt khỏi bất kỳ mã hex gắn cứng nào. Chúng khác biệt với màu decorative (bảng màu minh hoạ, gradient marketing, màu phân loại trong data visualization) không mang ý nghĩa semantic và không nên alias qua các tên kiểu color-danger — trộn hai loại này khiến team vô tình dùng lại “danger red” cho một điểm nhấn trang trí, và điều đó sẽ hỏng nếu danger được định nghĩa lại.
Dark mode là vấn đề đổi token, không phải một thiết kế thứ hai. Nếu component chỉ tiêu thụ token semantic (không bao giờ dùng giá trị hex thô hay thậm chí token global trực tiếp), thì hỗ trợ dark theme chỉ là việc định nghĩa lại tier semantic trỏ tới đâu — lớp component không bị đụng tới:
/* Light theme (mặc định) */
:root {
--color-surface: #ffffff;
--color-surface-subtle: var(--gray-50);
--color-text: var(--gray-900);
--color-text-muted: var(--gray-600);
--color-border: var(--gray-200);
--color-primary: var(--blue-600);
}
/* Dark theme — cùng tên semantic, nhưng token global đứng sau khác đi */
[data-theme='dark'] {
--color-surface: var(--gray-900);
--color-surface-subtle: var(--gray-800);
--color-text: var(--gray-50);
--color-text-muted: var(--gray-400);
--color-border: var(--gray-700);
--color-primary: var(--blue-500); /* dịch sáng hơn để đủ contrast trên nền tối */
}
.card {
background: var(--color-surface);
color: var(--color-text);
border: 1px solid var(--color-border);
}
.card không hề nhắc tới “dark” hay “light” — nó chỉ tiêu thụ --color-surface và --color-text, và scope [data-theme] đang active quyết định giá trị vật lý nào được resolve. Đây chính xác là lý do mô hình ba tier quan trọng: một component được viết dựa trên token semantic tự động sẵn sàng cho theming, không cần một dòng branching nào ở cấp component.
Typography như một scale, không phải danh sách kích thước rời rạc
Thay vì designer chọn font size tuỳ tiện (14px chỗ này, 15px chỗ kia, 18px chỗ khác), một type scale sinh ra một tập kích thước nhỏ, cố định từ một base size và một tỉ lệ, để toàn hệ thống đọc như được thiết kế có chủ đích thay vì tuỳ tiện. Một cách tiếp cận phổ biến là modular scale: chọn một base (ví dụ 16px) và một ratio (ví dụ 1.25, “major third”), sau đó nhân/chia lặp lại:
| Token | Công thức | Giá trị (làm tròn) | Cách dùng tiêu biểu |
|---|---|---|---|
font-size-xs | base ÷ 1.25² | 10px | Caption, văn bản pháp lý |
font-size-sm | base ÷ 1.25 | 13px | Text phụ, label |
font-size-base | base | 16px | Body text |
font-size-lg | base × 1.25 | 20px | Đoạn dẫn nhập, H4 |
font-size-xl | base × 1.25² | 25px | H3 |
font-size-2xl | base × 1.25³ | 31px | H2 |
font-size-3xl | base × 1.25⁴ | 39px | H1 |
Các giá trị này trở thành token giống hệt như màu sắc, và mọi quyết định liên quan tới typography — family, weight, line-height, letter-spacing — được token hoá cùng với size để một “Heading 2” là một composite token (đôi khi gọi là typography token hay type style) thay vì bốn giá trị rời rạc mà developer phải tự nhớ để kết hợp đúng:
:root {
--font-family-base: 'Inter', system-ui, sans-serif;
--font-family-mono: 'JetBrains Mono', monospace;
--font-weight-regular: 400;
--font-weight-medium: 500;
--font-weight-bold: 700;
--font-size-base: 1rem; /* 16px */
--font-size-2xl: 1.953rem; /* ~31px */
--line-height-tight: 1.2;
--line-height-normal: 1.5;
}
.heading-2 {
font-family: var(--font-family-base);
font-size: var(--font-size-2xl);
font-weight: var(--font-weight-bold);
line-height: var(--line-height-tight);
}
Trong định dạng DTCG JSON, ý tưởng composite này được diễn đạt bằng $type: "typography" và một giá trị dạng object:
{
"typography": {
"heading-2": {
"$type": "typography",
"$value": {
"fontFamily": "{font.family.base}",
"fontWeight": "{font.weight.bold}",
"fontSize": "{font.size.2xl}",
"lineHeight": "{line-height.tight}"
}
}
}
}
Spacing & sizing: một scale bị giới hạn tốt hơn giá trị pixel tuỳ tiện
Cùng logic sinh ra type scale cũng sinh ra spacing scale: thay vì cho phép bất kỳ giá trị pixel nào (13px, 17px, 22px rải rác trong margin và padding), hệ thống định nghĩa một tập nhỏ các bước — phổ biến nhất là 8-point scale (0, 4, 8, 16, 24, 32, 48, 64...) hoặc 4-point scale cho UI dày đặc hơn — và mọi margin, padding, gap trong sản phẩm là một trong các bước đó.
| Token | Giá trị (8pt scale) | rem (base 16px) |
|---|---|---|
spacing-0 | 0px | 0rem |
spacing-1 | 4px | 0.25rem |
spacing-2 | 8px | 0.5rem |
spacing-4 | 16px | 1rem |
spacing-6 | 24px | 1.5rem |
spacing-8 | 32px | 2rem |
spacing-12 | 48px | 3rem |
spacing-16 | 64px | 4rem |
Một scale bị giới hạn không phải là hạn chế — đó chính là mục đích. Nó giới hạn số quyết định mà designer hay developer phải đưa ra (chọn từ 8 giá trị, không phải bất kỳ giá trị nào trong 1.000 mức pixel có thể), nó đảm bảo nhịp điệu thị giác (visual rhythm) xuyên suốt cả sản phẩm vì mọi thứ đều căn theo một đơn vị chung, và nó khiến token thực sự tái sử dụng được: spacing-4 mang nghĩa “một đơn vị khoảng thở” giống nhau ở mọi nơi nó được dùng, nên một developer đọc padding: var(--spacing-4) hiểu được ý định mà không cần mở file thiết kế.
Đơn vị: rem so với px. Token cho web thường được định nghĩa bằng rem thay vì px vì một lý do vận hành: rem tương đối với font size gốc, nên một người dùng (hoặc một cài đặt accessibility ở cấp OS) tăng font size gốc sẽ khiến spacing và layout scale tỉ lệ cùng với text, trong khi token dựa trên px giữ cố định và có thể khiến text tràn ra ngoài container. Style Dictionary và các công cụ tương tự thường định nghĩa giá trị nguồn bằng đơn vị trung lập theo platform (hoặc bằng px làm đơn vị tham chiếu) và transform nó sang rem cho output CSS, sang pt/dp cho iOS/Android.
Sizing token mở rộng cùng scale đó cho kích thước component — kích thước container icon, kích thước avatar, chiều cao tap-target tối thiểu — để chiều cao của một button và bounding box của một icon đều được lấy từ cùng một tập giới hạn thay vì được nghĩ ra riêng lẻ cho từng component:
:root {
--size-icon-sm: var(--spacing-4); /* 16px */
--size-icon-md: var(--spacing-6); /* 24px */
--size-icon-lg: var(--spacing-8); /* 32px */
--size-tap-target-min: 2.75rem; /* 44px — mức tối thiểu theo WCAG 2.5.5 */
}
Grid & layout
Một grid system chia không gian ngang của layout thành một số cột bằng nhau cố định cộng với gutter nhất quán, cho designer và developer một ngôn ngữ chung để căn chỉnh (“card này chiếm 4 trên 12 cột”) thay vì ước lượng width bằng mắt. Quy ước phổ biến nhất trên web là grid 12 cột vì 12 chia đều thành nửa, phần ba, phần tư (6, 4, 3, 2 cột), bao phủ hầu hết các cách chia layout thông dụng mà không dư số.
| Token | Giá trị | Mục đích |
|---|---|---|
grid-columns | 12 | Tổng số cột |
grid-gutter | spacing-6 (24px) | Khoảng cách giữa các cột |
grid-margin | spacing-4 (16px) trên mobile, spacing-8 (32px) trên desktop | Padding mép ngoài |
container-max-width | 1280px | Chiều rộng nội dung tối đa trước khi căn giữa bằng margin |
Grid không tồn tại độc lập — chúng đi kèm với breakpoint, vì số cột thực dụng, kích thước gutter, và margin ngoài thường thay đổi tại mỗi breakpoint (một grid desktop 12 cột thường thu gọn thành 4 cột trên mobile, vì 12 cột hẹp quá mảnh để hữu dụng ở màn hình rộng 375px).
Breakpoint & responsiveness
Breakpoint cũng là token — các ngưỡng độ rộng viewport có tên, tại đó layout, grid, và đôi khi cả typography thay đổi:
:root {
--breakpoint-sm: 640px;
--breakpoint-md: 768px;
--breakpoint-lg: 1024px;
--breakpoint-xl: 1280px;
--breakpoint-2xl: 1536px;
}
Các giá trị cụ thể này được dùng rộng rãi trong Tailwind CSS, các hệ thống gần giống Bootstrap, và preset breakpoint mặc định của hầu hết các công cụ thiết kế, dù mỗi hệ thống hoàn toàn có thể tự định nghĩa bộ giá trị riêng để khớp với dữ liệu sử dụng thiết bị thực tế của mình.
CSS custom properties hiện tại không thể dùng trực tiếp trong điều kiện của @media (@media (min-width: var(--breakpoint-md)) không phải CSS hợp lệ tại thời điểm viết bài này), nên breakpoint token thường được tiêu thụ qua một preprocessor hoặc build step (biến Sass, PostCSS custom media, hoặc một Tailwind config phía JS) inline giá trị thô tại thời điểm build, trong khi vẫn được sinh ra từ cùng một nguồn token duy nhất như mọi token khác:
/* PostCSS custom media (plugin postcss-custom-media) — được resolve lúc build */
@custom-media --md (min-width: 768px);
.layout {
grid-template-columns: 1fr;
}
@media (--md) {
.layout {
grid-template-columns: repeat(12, 1fr);
}
}
Mobile-first so với desktop-first là một lựa chọn chiến lược về việc breakpoint token nào là style mặc định (không cần điều kiện) và style nào cần media query để override. Mobile-first (cách tiếp cận phổ biến hơn trong các hệ thống hiện đại) viết layout ở viewport nhỏ nhất làm style cơ sở và thêm các media query min-width để dần bổ sung độ phức tạp cho màn hình lớn hơn — điều này khớp với thực tế là layout mobile bị giới hạn thường đơn giản hơn, và có nghĩa là một trình duyệt không khớp bất kỳ media query nào vẫn nhận được layout (mobile) dùng được. Desktop-first đảo ngược điều này: style cơ sở nhắm tới desktop, và các query max-width gỡ bỏ độ phức tạp cho viewport nhỏ hơn. Dù chọn hướng nào, đó cũng nên là một quy ước được tài liệu hoá, áp dụng toàn hệ thống — trộn cả hai trong cùng một codebase sẽ tạo ra các breakpoint token xung đột nhau (một override min-width: md va chạm với một override max-width: lg trên cùng một element).
Icon
Icon cũng là nền tảng thị giác, và hưởng lợi từ cùng cách token hoá như màu sắc và spacing:
- Sizing token. Icon hầu như luôn bị giới hạn theo cùng scale với sizing token ở trên (
size-icon-sm/md/lg), thay vì được scale tuỳ tiện theo từng instance, để một icon ở mức “medium” luôn chiếm cùng một diện tích cạnh text ở bước type-scale tương ứng. - Reserved icons (icon dành riêng). Một số icon mang ý nghĩa cố định, xuyên toàn hệ thống theo quy ước platform hoặc kỳ vọng về accessibility — dấu tick cho “success/confirmed,” dấu “X” cho “close/dismiss,” spinner cho “loading,” tam giác chấm than cho “warning.” Một design system nên tài liệu hoá rõ ràng những icon này là reserved: chúng không được tái sử dụng cho một hành động không liên quan ở nơi khác trong sản phẩm, vì làm vậy sẽ phá vỡ liên tưởng mà người dùng đã học được từ phần còn lại của platform (và thường cả từ bên ngoài nó — các quy ước ở cấp OS).
- Hệ icon monochrome so với multi-color. Một bộ icon monochrome (chỉ một màu fill
currentColor, không có màu gắn sẵn) là mặc định cho hệ icon UI vì nó cho phép một file icon được recolor qua CSS/tint để khớp với bất kỳ token semantic nào (color-danger,color-text-muted) mà không cần duy trì các biến thể màu; đây là điều cho phép một icon component nhận một propcolorgiống như text. Bộ icon multi-color (logo thương hiệu, icon minh hoạ có màu gắn sẵn) cố ý không theo quy tắc này, vì màu sắc là một phần bản sắc của chúng — nhưng một design system nên giữ hai nhóm này tách biệt rõ ràng (ví dụ, thư viện Figma khác nhau, component API khác nhau) để một consumer đang dùng icon monochrome không vô tình lấy nhầm asset multi-color và có được một icon bỏ qua theme xung quanh.
Bảng tham chiếu tier
| Tier | Ví dụ token | Điều gì thay đổi khi bạn sửa nó |
|---|---|---|
| Global / reference | blue-600: #2563eb | Chỉ những component/token trực tiếp tham chiếu blue-600 — thường chỉ là các alias ở tier 2, nếu kỷ luật phân tier được tuân thủ. Sửa giá trị này thành một hue khác thực chất là định nghĩa một màu mới, không phải re-theme. |
| Semantic / alias | color-primary: {blue-600} | Mọi token component alias tới color-primary — button, link, focus ring, mục nav đang active — tự động re-theme mà không cần đụng vào code component. Đây là tier bạn sửa khi rebrand hoặc đổi light/dark. |
| Component | button-background: {color-primary} | Chỉ một component đó (và các variant của nó) tiêu thụ token này — ví dụ, làm primary button có sắc thái hơi khác link, mà không ảnh hưởng tới link. |
Best Practices
Giữ kỷ luật tier nghiêm túc: component nên tiêu thụ component token (hoặc, nếu không có, semantic token) và không bao giờ vượt qua chúng để chạm tới token global hay giá trị hex thô trực tiếp — ngay khi một component hardcode #2563eb hoặc tham chiếu trực tiếp --blue-600, nó đã tự loại mình khỏi khả năng theming và an toàn khi rebrand. Enforce điều này bằng linting khi có thể (rule stylelint cảnh báo giá trị hex/rgb thô, hoặc cấm tham chiếu --blue-* bên ngoài chính các file định nghĩa token) thay vì chỉ dựa vào code review.
Đặt tên token theo vai trò, không theo hình thức, ở tier semantic trở lên. color-primary sống sót qua một lần rebrand; color-blue thì không — nếu màu thương hiệu đổi sang tím, một token đặt tên literal là color-blue nhưng trỏ tới giá trị tím sẽ gây khó hiểu cho mọi người đọc sau này. Chỉ dành tên theo hình thức (blue-600) cho tier global, nơi chúng chính xác theo định nghĩa.
Version và tài liệu hoá token như một API, vì đó chính xác là bản chất của nó đối với mọi team tiêu thụ design system. Đổi tên hay xoá một token là một breaking change y hệt việc đổi function signature; deprecate trước khi xoá, và thông báo qua cùng quy trình release notes dùng cho thay đổi component (xem Tooling & Quy trình Design-to-Dev để biết pipeline biến chỉnh sửa token thành package được publish).
Định nghĩa token một lần, trong một nguồn độc lập với platform (DTCG JSON hoặc định dạng tương thích Style Dictionary), và sinh mọi output platform từ đó — không bao giờ duy trì thủ công các file token song song cho iOS, Android, và web, vì chúng sẽ trôi lệch (drift) ngay khi ai đó chỉ sửa một trong số đó.
Giữ các scale thực sự bị giới hạn. Giá trị của một 8-point spacing scale hay một modular type scale đến hoàn toàn từ việc không tạo ngoại lệ; ngoại lệ đầu tiên kiểu “chỉ lần này thôi, 13px” làm xói mòn lý do tồn tại của cả scale và mở lại cánh cửa cho các giá trị tuỳ tiện. Nếu thực sự có một khoảng trống trong scale, hãy thêm một token có chủ đích (có tên và lý do được tài liệu hoá) thay vì dùng giá trị thô inline.
Coi dark mode (và bất kỳ theme bổ sung nào — high-contrast, theme white-label riêng theo thương hiệu) là các tập token ở tier semantic thay thế, không bao giờ là một tập component song song hay một class .dark-mode rải rác trong CSS của component. Nếu một component cần logic branching kiểu if (darkMode) { ... } để hiển thị đúng, các token semantic bên dưới nó chưa đầy đủ.
Tài liệu hoá rõ ràng các reserved icon và functional color trong cùng tài liệu tham chiếu với token (không chỉ trong công cụ thiết kế) để engineer chưa từng mở Figma vẫn biết rằng icon tam giác cảnh báo và token color-danger được dành riêng cho ý nghĩa cụ thể của chúng.
Tài liệu tham khảo
- W3C Design Tokens Community Group — Format Specification
- Design Tokens Community Group
- Material Design 3 — The Color System
- Amazon Style Dictionary — Documentation
- Tailwind CSS — Customizing Colors & Theme
- Nathan Curtis — Naming Tokens in Design Systems
- WCAG 2.1 Success Criterion 2.5.5 — Target Size
Xem thêm: Design Language & Nguyên tắc thiết kế · Component cốt lõi — Input & Hành động · Tooling & Quy trình Design-to-Dev
Part of the Design System Roadmap knowledge base.
Overview
A design token is the smallest, named unit of a design decision: a color, a spacing value, a font size, a border radius, an animation duration. Instead of a developer typing #3B82F6 into a stylesheet and a designer picking the same blue from a swatch by eye, both reference one token — color-primary — that is defined once and consumed everywhere: web CSS, iOS UIColor, Android colorResource, Figma styles.
The payoff is not aesthetic, it is operational. Without tokens, a rebrand or a “make it support dark mode” request means grepping for hex codes across hundreds of files, in three codebases, hoping nothing was missed. With tokens, the same change is editing one JSON file (or one set of CSS custom properties) and rebuilding — every platform picks up the new value automatically, because nothing downstream ever hardcoded the raw value. Tokens are what let a design system separate a decision (this is our brand blue) from every place that decision is applied (buttons, links, focus rings, charts).
This note covers how tokens are structured in tiers, the file formats used to define and ship them (CSS custom properties, the W3C Design Tokens JSON format, Style Dictionary build pipelines), and how the major visual foundations of a design system — color, typography, spacing, grid, breakpoints, and icons — are themselves expressed as tokens rather than as one-off values baked into components. It pairs with Design Language & Principles (the why behind these values), feeds into Core Components — Inputs & Actions (which consume component-tier tokens), and is operationalized by the pipelines described in Tooling & Design-to-Dev Workflow.
Fundamentals
What makes something a token, not just a value
A raw value (16px, #2563EB) becomes a design token when it has:
- A name that describes its role or place in a scale (
spacing-4,blue-600,color-text-danger) rather than its literal content. - A single source of truth — one file or system where it is defined, from which every platform-specific representation is generated or referenced.
- Platform-agnostic meaning — the token
spacing-4means “16px on web, 16pt on iOS, 16dp on Android,” the same design intent expressed in each platform’s native unit. - Traceability — you can point at any pixel in the product and say which token produced it, and conversely, changing a token lets you predict every place affected.
A one-off value like margin-top: 13px scattered in a component’s CSS is not a token — it is exactly the kind of undocumented, un-reusable decision that tokens exist to eliminate.
The three tiers: global, semantic, component
Design tokens are almost universally organized in a layered hierarchy, commonly with three tiers. Each tier references the tier below it, so changes cascade downward without ever touching raw values twice.
Tier 1 — Global / reference tokens. The raw design palette: every color, every spacing step, every font size the system defines, named after what it is, not what it’s for. These have no opinion about usage.
/* Tier 1: global/reference tokens — the raw palette, named descriptively */
:root {
--blue-50: #eff6ff;
--blue-100: #dbeafe;
--blue-500: #3b82f6;
--blue-600: #2563eb;
--blue-700: #1d4ed8;
--red-500: #ef4444;
--red-600: #dc2626;
--gray-50: #f9fafb;
--gray-900: #111827;
}
Tier 2 — Semantic / alias tokens. Reference tier-1 tokens but are named after intent or role: color-primary, color-danger, color-surface. This is the tier that changes when you re-theme — swap what color-primary points to, and every consumer following the chain updates without editing a single component.
/* Tier 2: semantic/alias tokens — named by role, point at tier 1 */
:root {
--color-primary: var(--blue-600);
--color-primary-hover: var(--blue-700);
--color-primary-subtle: var(--blue-50);
--color-danger: var(--red-600);
--color-surface: #ffffff;
--color-text: var(--gray-900);
}
Tier 3 — Component tokens. Reference tier-2 (or occasionally tier-1) tokens but are scoped to one component’s specific properties. This is the tier a component library actually consumes in its stylesheets.
/* Tier 3: component tokens — scoped to a specific component and property */
:root {
--button-background: var(--color-primary);
--button-background-hover: var(--color-primary-hover);
--button-text-color: #ffffff;
--button-border-radius: var(--radius-md);
--input-border-color: var(--gray-900);
--input-border-color-error: var(--color-danger);
}
The chain blue-600 → color-primary → button-background is the entire point of tiering. Rebrand from blue to purple: edit what color-primary aliases to in tier 2, and the button, every link, every focus ring downstream updates — nobody touches button.css. Need a component to deviate from the theme for a one-off reason: edit only the tier-3 token, and the blast radius is contained to that one component.
Token formats
CSS custom properties are the simplest and most widely supported way to ship tokens for the web. They cascade, they can be overridden per-scope (e.g., per theme, per component), and they’re readable directly by browsers with no build step:
:root {
--color-primary: #2563eb;
--spacing-4: 1rem;
--font-size-body: 1rem;
--radius-md: 0.5rem;
}
.button {
background: var(--color-primary);
padding: var(--spacing-4);
border-radius: var(--radius-md);
}
Their limitation: CSS custom properties are a runtime format for one platform (the web). They don’t natively describe an iOS color asset or an Android dimension resource, and they carry no metadata (description, type, deprecation status). For a multi-platform design system, teams instead maintain a platform-agnostic source of truth and generate CSS custom properties (along with iOS/Android/Figma formats) from it as a build output.
The W3C Design Tokens Community Group (DTCG) format is the emerging standard for that source of truth: a JSON file where each token has a $value, a $type, and optional $description, and groups nest to express tiers:
{
"color": {
"blue": {
"500": { "$value": "#3b82f6", "$type": "color" },
"600": { "$value": "#2563eb", "$type": "color" }
}
},
"semantic": {
"color": {
"primary": {
"$value": "{color.blue.600}",
"$type": "color",
"$description": "Default brand action color; used for primary buttons, links, and focus rings."
},
"primary-hover": {
"$value": "{color.blue.700}",
"$type": "color"
}
}
},
"component": {
"button": {
"background": {
"$value": "{semantic.color.primary}",
"$type": "color"
}
}
},
"spacing": {
"4": { "$value": "16px", "$type": "dimension" }
}
}
The {color.blue.600} syntax is a token reference — exactly the tier-2-points-to-tier-1 relationship from the CSS example above, but expressed in a format any build tool can parse and resolve, regardless of target platform.
Build pipelines: turning one source into many outputs
Style Dictionary (originally built at Amazon, now widely adopted) is the de facto standard tool for this transformation: you author tokens once (in DTCG JSON, YAML, or its own JSON5-ish format) and configure “platforms,” each with a set of transforms and a file format, and it generates output for each:
// style-dictionary.config.js
module.exports = {
source: ['tokens/**/*.json'],
platforms: {
css: {
transformGroup: 'css',
buildPath: 'build/css/',
files: [{ destination: 'variables.css', format: 'css/variables' }]
},
ios: {
transformGroup: 'ios',
buildPath: 'build/ios/',
files: [{ destination: 'Tokens.swift', format: 'ios-swift/class.swift' }]
},
android: {
transformGroup: 'android',
buildPath: 'build/android/',
files: [{ destination: 'tokens.xml', format: 'android/resources' }]
}
}
};
Running style-dictionary build against a token source like the DTCG JSON above produces, among other things, a variables.css with --color-blue-600: #2563eb;, a Tokens.swift with static let colorBlue600 = UIColor(...), and an android/tokens.xml with <color name="color_blue_600">#2563EB</color> — three platform-native artifacts from one authored source, with transforms handling unit conversion (px → pt, hex → platform color object) along the way. This is what makes “define once, consume everywhere” actually true in practice rather than aspirational: no human re-types the hex code for the Android team.
Key Concepts
Color: scales, functional colors, and dark mode
A production color system is rarely a flat list of named colors — it’s built from scales (ramps), typically 9–10 steps per hue (50, 100, 200, ... 900), where 50 is the near-white tint and 900 is the near-black shade. This is the same structure Tailwind CSS and Material Design both use, because it solves a real problem: given one brand hue, you need lighter and darker variants for hover states, subtle backgrounds, borders, and disabled states, and a scale gives you a predictable, perceptually-graduated set instead of ad hoc one-offs.
| Step | Typical use |
|---|---|
| 50–100 | Subtle backgrounds, selected-row highlight |
| 200–300 | Borders, dividers on colored surfaces |
| 400–500 | Icons, secondary emphasis |
| 600 | Default interactive color (button, link) |
| 700 | Hover / active state |
| 800–900 | Text-on-light, high-emphasis accents |
On top of scales sit functional (semantic) colors — success, warning, danger, info — which are a specialized case of the semantic tier: each maps to a scale (often a different hue entirely, e.g. success → green-600, danger → red-600) so that meaning is separated from any single hardcoded hex. These are distinct from decorative colors (illustration palettes, marketing gradients, data-visualization categorical colors) which don’t carry semantic meaning and shouldn’t be aliased through color-danger-style names — mixing the two causes teams to accidentally reuse a “danger red” for a decorative accent, which then breaks if danger is redefined.
Dark mode is a token-swapping problem, not a second design. If components only ever consume semantic tokens (never raw hex values or even global tokens directly), then supporting a dark theme is a matter of redefining what the semantic tier points to — the component layer is untouched:
/* Light theme (default) */
:root {
--color-surface: #ffffff;
--color-surface-subtle: var(--gray-50);
--color-text: var(--gray-900);
--color-text-muted: var(--gray-600);
--color-border: var(--gray-200);
--color-primary: var(--blue-600);
}
/* Dark theme — same semantic names, different global tokens behind them */
[data-theme='dark'] {
--color-surface: var(--gray-900);
--color-surface-subtle: var(--gray-800);
--color-text: var(--gray-50);
--color-text-muted: var(--gray-400);
--color-border: var(--gray-700);
--color-primary: var(--blue-500); /* shifted lighter for contrast on dark surfaces */
}
.card {
background: var(--color-surface);
color: var(--color-text);
border: 1px solid var(--color-border);
}
.card never mentions “dark” or “light” — it consumes --color-surface and --color-text, and the active [data-theme] scope decides which physical color that resolves to. This is precisely why the three-tier model matters: a component authored against semantic tokens is automatically theme-ready, with zero component-level branching.
Typography as a scale, not a list of sizes
Rather than designers picking font sizes ad hoc (14px here, 15px there, 18px over there), a type scale derives a small, fixed set of sizes from a base size and a ratio, so the whole system reads as deliberately related rather than arbitrary. A common approach is a modular scale: pick a base (e.g., 16px) and a ratio (e.g., 1.25, the “major third”), then multiply/divide repeatedly:
| Token | Formula | Value (rounded) | Typical use |
|---|---|---|---|
font-size-xs | base ÷ 1.25² | 10px | Captions, legal text |
font-size-sm | base ÷ 1.25 | 13px | Secondary text, labels |
font-size-base | base | 16px | Body text |
font-size-lg | base × 1.25 | 20px | Lead paragraphs, H4 |
font-size-xl | base × 1.25² | 25px | H3 |
font-size-2xl | base × 1.25³ | 31px | H2 |
font-size-3xl | base × 1.25⁴ | 39px | H1 |
These become tokens exactly like colors do, and every typography-related decision — family, weight, line-height, letter-spacing — is tokenized alongside size so that a “Heading 2” is a composite token (sometimes called a typography token or type style) rather than four separate values a developer has to remember to combine correctly:
:root {
--font-family-base: 'Inter', system-ui, sans-serif;
--font-family-mono: 'JetBrains Mono', monospace;
--font-weight-regular: 400;
--font-weight-medium: 500;
--font-weight-bold: 700;
--font-size-base: 1rem; /* 16px */
--font-size-2xl: 1.953rem; /* ~31px */
--line-height-tight: 1.2;
--line-height-normal: 1.5;
}
.heading-2 {
font-family: var(--font-family-base);
font-size: var(--font-size-2xl);
font-weight: var(--font-weight-bold);
line-height: var(--line-height-tight);
}
In the DTCG JSON format, this composite idea is expressed with $type: "typography" and an object value:
{
"typography": {
"heading-2": {
"$type": "typography",
"$value": {
"fontFamily": "{font.family.base}",
"fontWeight": "{font.weight.bold}",
"fontSize": "{font.size.2xl}",
"lineHeight": "{line-height.tight}"
}
}
}
}
Spacing & sizing: a constrained scale beats arbitrary pixels
The same logic that produces a type scale produces a spacing scale: rather than allowing any pixel value (13px, 17px, 22px scattered across margins and padding), the system defines a small set of steps — most commonly an 8-point scale (0, 4, 8, 16, 24, 32, 48, 64...) or a 4-point scale for denser UI — and every margin, padding, and gap in the product is one of those steps.
| Token | Value (8pt scale) | rem (base 16px) |
|---|---|---|
spacing-0 | 0px | 0rem |
spacing-1 | 4px | 0.25rem |
spacing-2 | 8px | 0.5rem |
spacing-4 | 16px | 1rem |
spacing-6 | 24px | 1.5rem |
spacing-8 | 32px | 2rem |
spacing-12 | 48px | 3rem |
spacing-16 | 64px | 4rem |
A constrained scale isn’t a limitation — it’s the point. It bounds the decisions a designer or developer has to make (pick from 8 values, not any of 1,000 possible pixel amounts), it guarantees visual rhythm across a whole product because everything aligns to a common unit, and it makes tokens meaningfully reusable: spacing-4 means the same “one unit of breathing room” everywhere it’s used, so a developer reading padding: var(--spacing-4) understands the intent without opening a design file.
Units: rem vs px. Tokens for the web are conventionally authored in rem rather than px for one operational reason: rem is relative to the root font size, so a user (or an OS-level accessibility setting) who increases their base font size scales spacing and layout proportionally along with text, whereas px-based tokens stay fixed and can cause text to overflow its container. Style Dictionary and similar tools typically author the source value in a platform-neutral unit (or in px as the reference unit) and transform it to rem for CSS output and to pt/dp for iOS/Android.
Sizing tokens extend the same scale to component dimensions — icon container size, avatar size, minimum tap-target height — so that a button’s height and an icon’s bounding box are pulled from the same constrained set rather than invented per component:
:root {
--size-icon-sm: var(--spacing-4); /* 16px */
--size-icon-md: var(--spacing-6); /* 24px */
--size-icon-lg: var(--spacing-8); /* 32px */
--size-tap-target-min: 2.75rem; /* 44px — WCAG 2.5.5 minimum */
}
Grid & layout
A grid system divides the horizontal space of a layout into a fixed number of equal columns plus consistent gutters, giving designers and developers a shared vocabulary for alignment (“this card spans 4 of 12 columns”) instead of eyeballing widths. The most common web convention is a 12-column grid because 12 divides evenly into halves, thirds, and quarters (6, 4, 3, 2 columns), covering the most common layout splits without remainder.
| Token | Value | Purpose |
|---|---|---|
grid-columns | 12 | Total column count |
grid-gutter | spacing-6 (24px) | Space between columns |
grid-margin | spacing-4 (16px) on mobile, spacing-8 (32px) on desktop | Outer edge padding |
container-max-width | 1280px | Max content width before centering with margins |
Grids don’t live in isolation — they’re paired with breakpoints, because the practical column count, gutter size, and outer margin usually change at each breakpoint (a 12-column desktop grid commonly collapses to 4 columns on mobile, since 12 narrow columns are too thin to be useful at 375px wide).
Breakpoints & responsiveness
Breakpoints are tokens too — named viewport-width thresholds at which layout, grid, and sometimes typography change:
:root {
--breakpoint-sm: 640px;
--breakpoint-md: 768px;
--breakpoint-lg: 1024px;
--breakpoint-xl: 1280px;
--breakpoint-2xl: 1536px;
}
These specific values are widely shared across Tailwind CSS, Bootstrap-adjacent systems, and most design tools’ default breakpoint presets, though every system is free to define its own set to match its actual device-usage data.
CSS custom properties cannot currently be used directly inside a @media query condition (@media (min-width: var(--breakpoint-md)) is not valid CSS as of this writing), so breakpoint tokens are typically consumed by a preprocessor or build step (Sass variables, PostCSS custom media, or a JS-side Tailwind config) that inlines the raw value at build time, while still being generated from the same single token source as every other token:
/* PostCSS custom media (postcss-custom-media plugin) — resolved at build time */
@custom-media --md (min-width: 768px);
.layout {
grid-template-columns: 1fr;
}
@media (--md) {
.layout {
grid-template-columns: repeat(12, 1fr);
}
}
Mobile-first vs. desktop-first is a strategic choice about which breakpoint tokens are the default (unqualified) styles and which require a media query to override. Mobile-first (the more common approach in modern systems) writes the smallest-viewport layout as the base styles and layers on min-width media queries to progressively add complexity for larger screens — this matches how constrained mobile layouts are usually simpler, and it means a browser that fails to match any media query still gets a usable (mobile) layout. Desktop-first inverts this: base styles target desktop, and max-width queries strip complexity down for smaller viewports. Whichever direction is chosen, it should be a documented, system-wide convention — mixing both within one codebase produces breakpoint tokens that fight each other (a min-width: md override colliding with a max-width: lg override on the same element).
Icons
Icons are visual foundations too, and benefit from the same tokenization as color and spacing:
- Sizing tokens. Icons are almost always constrained to the same scale as sizing tokens above (
size-icon-sm/md/lg), rather than being scaled arbitrarily per instance, so an icon at “medium” always occupies the same footprint next to text set at the corresponding type-scale step. - Reserved icons. Certain icons carry fixed, system-wide meaning by platform convention or accessibility expectation — a checkmark for “success/confirmed,” an “X” for “close/dismiss,” a spinner for “loading,” a triangle-exclamation for “warning.” A design system should explicitly document these as reserved: they must not be repurposed for an unrelated action elsewhere in the product, because doing so breaks the learned association users bring from the rest of the platform (and often from outside it — OS-level conventions).
- Monochrome vs. multi-color icon systems. A monochrome icon set (single
currentColorfill, no embedded color) is the default for UI icon systems because it lets one icon file be recolored via CSS/tint to match any semantic token (color-danger,color-text-muted) without maintaining color variants; this is what lets an icon component accept acolorprop the same way text does. Multi-color icon sets (brand logos, illustrative icons with baked-in color) intentionally do not follow this rule, since their color is part of their identity — but a design system should keep the two populations clearly separated (e.g., different Figma libraries, different component APIs) so a monochrome-icon consumer doesn’t accidentally reach for a multi-color asset and end up with an icon that ignores the surrounding theme.
Tier reference table
| Tier | Example token | What changes when you edit it |
|---|---|---|
| Global / reference | blue-600: #2563eb | Only components/tokens that explicitly reference blue-600 directly — usually just tier-2 aliases, if the tiering discipline is followed. Editing this to a different hue is effectively defining a new color, not retheming. |
| Semantic / alias | color-primary: {blue-600} | Every component token that aliases color-primary — buttons, links, focus rings, active nav items — instantly re-themes without touching component code. This is the tier you edit for a rebrand or a light/dark swap. |
| Component | button-background: {color-primary} | Only the one component (and its variants) that consumes this token — e.g., making primary buttons a slightly different shade than links, without affecting links. |
Best Practices
Keep the tiers honest: components should consume component tokens (or, failing that, semantic tokens) and never reach past them to a global token or a raw hex value directly — the moment a component hardcodes #2563eb or references --blue-600 directly, it has opted out of theming and rebrand safety. Enforce this with linting where possible (stylelint rules that flag raw hex/rgb values, or forbid referencing --blue-* outside the token definition files themselves) rather than relying on code review alone.
Name tokens by role, not by appearance, at the semantic tier and above. color-primary survives a rebrand; color-blue does not — if the brand color changes to purple, a token literally named color-blue pointing at a purple value is confusing to every future reader. Reserve appearance-based names (blue-600) strictly for the global tier, where they are accurate by definition.
Version and document tokens like an API, because that is what they are to every team consuming the design system. A token rename or removal is a breaking change exactly like a function signature change; deprecate before removing, and communicate through the same release-notes process used for component changes (see Tooling & Design-to-Dev Workflow for the pipeline that turns token edits into published packages).
Author tokens once, in a platform-agnostic source (DTCG JSON or a Style Dictionary-compatible format), and generate every platform output from it — never hand-maintain parallel token files for iOS, Android, and web, since they will drift the first time someone edits only one.
Keep scales genuinely constrained. The value of an 8-point spacing scale or a modular type scale comes entirely from not making exceptions; the first “just this once, 13px” erodes the rationale for the whole scale and reopens the door to arbitrary values. If a real gap exists in the scale, add a token deliberately (with a name and a documented reason) rather than reaching for a raw value inline.
Treat dark mode (and any additional themes — high-contrast, brand-specific white-label themes) as alternate semantic-tier token sets, never as a parallel set of components or a .dark-mode class sprinkled through component CSS. If a component needs if (darkMode) { ... } branching logic to look right, the semantic tokens beneath it are incomplete.
Document reserved icons and functional colors explicitly in the same reference material as tokens (not just in a design tool) so engineers who never open Figma still know that the warning-triangle icon and color-danger token are reserved for their specific meanings.
References
- W3C Design Tokens Community Group — Format Specification
- Design Tokens Community Group
- Material Design 3 — The Color System
- Amazon Style Dictionary — Documentation
- Tailwind CSS — Customizing Colors & Theme
- Nathan Curtis — Naming Tokens in Design Systems
- WCAG 2.1 Success Criterion 2.5.5 — Target Size
See also: Design Language & Principles · Core Components — Inputs & Actions · Tooling & Design-to-Dev Workflow