Bỏ qua tới nội dung
UI/UX Design· ·7 phút đọc

Dựng design system với Tailwind CSS (cấu hình theme, token và component)

Nguyễn Quốc An
Dựng design system với Tailwind CSS (cấu hình theme, token và component)
Cỡ chữ

Tailwind CSS thường bị hiểu nhầm chỉ là “đống class viết tắt”. Thực chất, khi dùng đúng, nó là một cách lưu trữ design system rất gọn: bạn khai báo biến thiết kế một lần ở tầng theme, và Tailwind tự sinh ra hệ tiện ích đồng nhất cho toàn bộ giao diện. Khác với dựng design system bằng shadcn/ui (một bộ component cụ thể bạn copy vào dự án), Tailwind là nền tổng quát — bạn tự định nghĩa hệ token và component theo thương hiệu riêng.

Vì sao utility-first hợp để lưu design token

Một design token (biến thiết kế — đơn vị nhỏ nhất lưu màu, cỡ chữ, khoảng cách) chỉ có giá trị khi nó được dùng nhất quán. Vấn đề của CSS truyền thống là token dễ “trôi”: màu primary lưu một nơi, nhưng dev vẫn gõ tay #FF6986 ở chỗ khác vì tiện. Lâu dần hệ màu vỡ.

Mô hình utility-first (ưu tiên lớp tiện ích nhỏ) xử lý điểm này theo cách khá thông minh: token và class dùng được buộc chặt vào nhau. Khi bạn khai báo --color-primary, Tailwind chỉ sinh đúng class bg-primary, text-primary. Muốn một màu thì phải có token tương ứng — không có token thì không có class hợp lệ. Hệ ràng buộc này khiến việc đi ngoài design system trở nên bất tiện hơn việc tuân theo nó, nên đội phát triển tự nhiên đi đúng đường.

Sơ đồ các namespace token trong @theme Tailwind v4 và utility class chúng sinh ra
Mỗi token trong @theme sinh đồng thời một utility class và một biến CSS thật.

Khai báo token bằng @theme (Tailwind v4)

Từ phiên bản 4 (ra mắt đầu 2025, theo blog chính thức của Tailwind), cấu hình chuyển hẳn sang CSS-first: bạn không còn bắt buộc dùng file tailwind.config.js mà khai báo token thẳng trong CSS qua directive @theme. Đây chính là “trái tim” của design system.

@import "tailwindcss";

@theme {
  /* Màu thương hiệu */
  --color-primary: oklch(0.72 0.18 0);
  --color-ink: #1a1a1a;
  --color-surface: #fafaf8;

  /* Chữ */
  --font-display: "Inter Tight", sans-serif;
  --text-h1: 2.5rem;

  /* Khoảng cách & bo góc */
  --spacing: 0.25rem;
  --radius-card: 0.75rem;
}

Điểm hay nhất, theo tài liệu Tailwind: mỗi token trong @theme sinh ra đồng thời hai thứ — một utility class (bg-primary, font-display) và một CSS variable thật trong :root (var(--color-primary)). Nhờ vậy bạn dùng được token cả qua class lẫn qua CSS thuần hay JavaScript, không phải đồng bộ hai nơi.

Các namespace (vùng đặt tên) quan trọng cần nhớ:

NamespaceSinh ra utilityVí dụ class
--color-*Màubg-primary, text-ink
--spacing-*Khoảng cách, kích thướcp-4, gap-2
--font-*Họ chữfont-display
--text-*Cỡ chữtext-h1
--radius-*Bo gócrounded-card
--shadow-*Đổ bóngshadow-md

Đặt token theo tầng: nền, ngữ nghĩa, component

Đừng nhét tất cả vào một mức. Thực hành phổ biến 2026 là chia ba tầng token:

  • Tầng nền (raw): giá trị thô — --color-coral-500, --color-coral-600. Đây là bảng màu vật lý.
  • Tầng ngữ nghĩa (semantic): gán vai trò — --color-primary: var(--color-coral-500), --color-danger, --color-surface. Component nên trỏ vào tầng này, không trỏ thẳng màu thô.
  • Tầng component: biến cục bộ của một thành phần, vẫn tham chiếu xuống tầng ngữ nghĩa.

Lợi ích thực chiến: muốn đổi nhận diện (vd chuyển primary từ coral sang xanh), bạn chỉ sửa một dòng ở tầng ngữ nghĩa, toàn bộ giao diện đổi theo. Cách phân tầng này cũng giúp làm dark mode (chế độ tối) sạch sẽ: chỉ cần ghi đè token ngữ nghĩa trong một khối điều kiện, component không phải sửa.

@apply và component — dùng đúng liều

Khi một mẫu lặp lại nhiều lần (ví dụ nút bấm), gom class bằng @apply để có một class component sạch:

@layer components {
  .btn-primary {
    @apply bg-primary text-white px-4 py-2 rounded-card font-medium;
  }
}

Lưu ý ở v4: @apply bị giới hạn trong file đã import Tailwind; nếu tách CSS module riêng bạn phải import lại. Và đây là cảnh báo quan trọng — đừng lạm dụng @apply. Nếu gói mọi thứ thành class component, bạn quay về đúng mô hình CSS truyền thống mà Tailwind muốn tránh. Thực tế, khi dùng framework component (React, Vue), tốt hơn là tạo component thật và để class utility ngay trong markup, chỉ dùng @apply cho những mẫu nhỏ tái dùng ngoài framework. Một thư viện component (tập hợp khối giao diện tái dùng) dựng kiểu này sẽ là nơi “đóng gói” design system thành đơn vị lắp ráp được.

Sơ đồ ba tầng đặt design token: tầng nền giá trị thô, tầng ngữ nghĩa gán vai trò, tầng component cục bộ
Chia token theo tầng giúp đổi nhận diện chỉ bằng một dòng ở tầng ngữ nghĩa.

Web22 nhìn từ thực chiến

web22.dev chạy chính hệ thống thiết kế kiểu này: token màu coral, font Inter Tight, bo góc và khoảng cách đều khai báo một chỗ rồi tái dùng khắp theme. Cái lợi rõ nhất không phải tốc độ gõ class, mà là handoff (bàn giao) không tam sao thất bản: vì Web22 vừa thiết kế vừa lập trình, token trong bản thiết kế và token trong code là cùng một bảng, nên giao diện ra đúng như bản vẽ.

Câu hỏi thường gặp

Tailwind có thay được design system không? Không hẳn. Tailwind là cơ chế lưu và áp dụng token rất tốt, nhưng quyết định màu nào, cỡ chữ nào, quy tắc dùng ra sao vẫn là phần thiết kế con người làm.

Nên dùng @theme hay tailwind.config.js? Với dự án mới trên v4, @theme là đường khuyến nghị. File config JS vẫn hoạt động nếu bạn cần logic động, nhưng phần lớn token nên ở CSS.

Khác gì với shadcn/ui? shadcn là bộ component cụ thể dựng trên Tailwind; bài này nói về nền Tailwind tổng quát để bạn tự định nghĩa hệ token và component riêng.

Nếu bạn cần một hệ thống thiết kế nhất quán từ token đến giao diện chạy thật, tham khảo dịch vụ thiết kế giao diện website của Web22.

Cần làm web?

Dịch vụ liên quan
tới chủ đề bài này.

Tất cả dịch vụ
Đọc tiếp

Bài viết
cùng chủ đề.

Tất cả bài viết