UX Architect

Design System: Xây dựng hệ sinh thái, không phải chỉ là thư viện component

7/19/2026 · 13p đọc


title: "Design System: Xây dựng hệ sinh thái, không phải chỉ là thư viện component"
series: "UX Architect: 40 bài Expert-Deep Dive"
part: "Phần 3 — UI Design System & Tính nhất quán"
order: 21
audience: "Product Designer & UX Architect"
reading_time: "13 phút"
tags: ["design system", "design tokens", "design governance", "component library", "design ops", "frontend architecture"]

Design System: Xây dựng hệ sinh thái, không phải chỉ là thư viện component

Một designer mới join team mở Figma, thấy file "Design System" với hơn 40 button variant — button xanh bo góc 4px, button xanh bo góc 8px, button xanh có shadow, button xanh viền... Không ai biết cái nào là "chuẩn". Cùng lúc đó, kỹ sư frontend mở codebase, thấy 6 file Button.tsx khác nhau nằm rải rác trong components/, shared/ui/, modules/billing/components/, mỗi cái style một kiểu vì được copy-paste rồi chỉnh tay qua nhiều sprint.

Đây không phải là thiếu design system — team này CÓ một file Figma được gắn nhãn "Design System". Vấn đề là cái họ có chỉ là một tủ đồ (closet) chứa component đẹp, không phải một hệ sinh thái có luật chơi. Không ai biết khi nào được tạo variant mới, ai duyệt, giá trị nào là nguồn sự thật khi Figma và code lệch nhau. Kết quả: mỗi team tự diễn giải "design system" theo cách của mình, và sản phẩm trông như được ghép từ 5 sản phẩm khác nhau.

Bài này đi sâu vào lý do một design system thật sự cần nhiều hơn một bộ component đẹp — nó cần các tầng kiến trúc rõ ràng và một mô hình quản trị (governance) sống được qua thời gian. Với vai trò UX Architect, đây là hạ tầng bạn chịu trách nhiệm thiết kế không kém gì một kỹ sư thiết kế database schema.

The UX Problem

Ngộ nhận phổ biến nhất về design system: "chúng tôi có design system" nghĩa là "chúng tôi có một file Figma chứa các component đã được style sẵn". Ngộ nhận này nguy hiểm vì nó dừng lại đúng ở phần dễ nhất — phần nhìn thấy được — và bỏ qua phần khó nhưng quyết định: quy tắc sử dụng và cơ chế duy trì tính nhất quán khi hệ thống lớn lên.

Hệ quả của ngộ nhận này lộ ra theo một trình tự rất dễ đoán:

  1. Giai đoạn honeymoon: Design system mới ra mắt, mọi thứ đẹp, đồng bộ. Designer khen nhau trong buổi demo.
  2. Giai đoạn drift (trôi dạt): Feature team A cần một button hơi khác, không có quy trình đề xuất chính thức nên designer tự tạo variant riêng trong file cá nhân. Feature team B gặp tình huống tương tự, làm y hệt nhưng với giá trị màu/spacing khác một chút.
  3. Giai đoạn phân mảnh: Sau 6-12 tháng, có 5-10 "phiên bản không chính thức" của mỗi component cốt lõi. Kỹ sư frontend, vì áp lực deadline, hardcode giá trị trực tiếp thay vì tham chiếu token — càng làm drift nặng hơn ở tầng code.
  4. Giai đoạn từ bỏ: Không ai còn tin vào "design system" nữa vì nó không còn phản ánh đúng sản phẩm thật. Người ta bắt đầu nói "cứ hỏi dev xem cái nào đang chạy thật" thay vì tra design system.

Cái gốc của vấn đề không phải là thiếu component — mà là thiếu ba thứ: (a) một cấu trúc phân tầng rõ ràng để biết cái gì là nguyên tử, cái gì là tổ hợp; (b) quy tắc sử dụng (guidelines) nói rõ khi nào dùng cái gì; và (c) một mô hình quản trị nói rõ ai có quyền thay đổi hệ thống. Thiếu ba thứ này, "design system" chỉ là một bộ sưu tập tĩnh, không phải một hệ sinh thái sống.

Design Framework

Một design system trưởng thành có bốn tầng, xếp từ nguyên tử đến bối cảnh sử dụng. Hiểu đúng thứ tự phụ thuộc giữa các tầng là chìa khóa để không rơi vào tình trạng "sửa một chỗ, vỡ mười chỗ".

Tầng 1 — Design Tokens (token thiết kế): Đây là đơn vị nguyên tử nhỏ nhất, không thể chia nhỏ hơn — màu sắc, khoảng cách (spacing), kích thước typography, độ bo góc (border-radius), độ cao bóng đổ (elevation/shadow), thời lượng animation. Token KHÔNG phải là component, nó là biến số. Ví dụ: color-primary-500 = #2563EB, spacing-md = 16px, font-size-body = 14px. Token là nơi bạn đặt "sự thật" duy nhất về giá trị hình ảnh của sản phẩm.

Tầng 2 — Components (thành phần): Đây là khối lắp ráp đầu tiên tiêu thụ token — Button, Input, Badge, Checkbox, Card. Một component tốt không tự định nghĩa màu #2563EB mà tham chiếu color-primary-500. Component có state (default, hover, disabled, error) và variant (size, hierarchy — primary/secondary/tertiary).

Tầng 3 — Patterns (khuôn mẫu kết hợp): Đây là tầng hay bị bỏ quên nhất. Pattern là cách nhiều component được kết hợp lại để giải quyết một tình huống cụ thể lặp lại nhiều lần — form đăng nhập, empty state, bảng dữ liệu có filter + pagination, modal xác nhận xoá. Pattern không phải là component đơn lẻ, nó là "công thức nấu ăn" dùng nhiều nguyên liệu component theo một trật tự đã được kiểm chứng là hiệu quả.

Tầng 4 — Guidelines (nguyên tắc sử dụng): Đây là tầng ngôn ngữ — văn bản giải thích triết lý, không phải hình ảnh. Guidelines trả lời câu hỏi "khi nào dùng cái gì": khi nào dùng button primary vs secondary, khoảng cách tối thiểu giữa hai action trong một form, giọng văn microcopy khi báo lỗi. Không có guidelines, component đẹp cỡ nào cũng bị dùng sai ngữ cảnh.

Song song với bốn tầng nội dung là mô hình quản trị (governance model) — quy định ai có quyền thêm/sửa ở mỗi tầng:

Tầng Ai được sửa Quy trình
Token Design system team / Design lead RFC + review kỹ thuật (ảnh hưởng toàn hệ thống)
Component Design system team, feature team đề xuất Đề xuất -> review -> merge vào core library
Pattern Feature team ghi nhận, DS team chuẩn hoá "Pattern hoá" sau khi dùng lặp lại ≥ 2-3 nơi
Guideline Design lead + UX Writing Cập nhật định kỳ, review theo quý

Ba mô hình quản trị phổ biến: Centralized (một team DS trung tâm kiểm soát mọi thay đổi — nhất quán cao nhưng chậm), Federated (mỗi feature team có quyền đóng góp qua quy trình PR-review — cân bằng tốc độ và kiểm soát), Distributed (ai cũng có thể tự thêm — nhanh nhưng rủi ro phân mảnh cao, chỉ phù hợp giai đoạn rất sớm). Phần lớn tổ chức tầm trung nên chọn Federated.

The "Architect's" Insight

Đây là nơi design system ngừng là chuyện của riêng designer và trở thành một quyết định kiến trúc hệ thống thật sự. Vấn đề cốt lõi: design token phải tồn tại dưới dạng MỘT nguồn dữ liệu duy nhất (single source of truth) mà cả công cụ thiết kế (Figma) lẫn codebase cùng tham chiếu — không phải hai bộ giá trị song song.

Hãy hình dung hai kiến trúc khác nhau cho cùng một design system:

Kiến trúc A — Hai nguồn sự thật (anti-pattern):

Figma: Styles panel có "Primary/500" = #2563EB (định nghĩa thủ công trong Figma)
Code:  theme.ts có PRIMARY_500 = '#2563EB' (định nghĩa thủ công trong code, do dev gõ tay khi đọc Figma)

Hai giá trị này BẰNG NHAU ở thời điểm ban đầu. Nhưng chúng là hai bản ghi độc lập, không có ràng buộc đồng bộ. Ba tháng sau, designer đổi Primary/500 thành #1D4ED8 để tăng độ tương phản, cập nhật trong Figma. Không ai báo cho kỹ sư. Code vẫn chạy #2563EB. Từ đây, mọi màn hình Figma và mọi màn hình thật của sản phẩm bắt đầu lệch nhau — đây chính là "token drift". Sau vài chu kỳ thay đổi như vậy, không ai còn biết Figma hay code mới là "đúng".

Kiến trúc B — Một nguồn sự thật (kiến trúc đúng):

tokens.json (nguồn sự thật duy nhất, version-controlled trong git)
  ├── build script (Style Dictionary hoặc tương tự)
  │     ├── xuất ra CSS custom properties  → codebase tham chiếu
  │     ├── xuất ra Figma Variables/Tokens plugin format → Figma tham chiếu
  │     └── xuất ra platform-specific format (iOS/Android nếu cần)

Khi đó, sửa color-primary chỉ cần sửa MỘT chỗ trong tokens.json, chạy build script, cả Figma và code đều tự động đồng bộ theo. Không còn bước "designer đổi Figma rồi thông báo bằng miệng/Slack cho dev đi tìm sửa thủ công từng chỗ hardcode".

Hệ quả kỹ thuật cụ thể của quyết định UX này (chọn kiến trúc single-source token) lan xuống nhiều tầng hệ thống:

  • Ở tầng codebase: Token không nên là hardcode rải rác (background-color: #2563EB xuất hiện ở 40 file), mà phải là biến CSS tập trung (background-color: var(--color-primary-500)) hoặc một object TypeScript/JSON được import (theme.colors.primary[500]). Nếu dùng CSS-in-JS hay Tailwind, token map trực tiếp vào file cấu hình (tailwind.config.js theme extend) — đây chính là điểm "codebase tham chiếu nguồn sự thật" cụ thể trong dự án FE Next.js/Tailwind của team.
  • Ở tầng build/CI: Cần một bước build tự động (thường dùng công cụ dạng Style Dictionary hoặc script tuỳ biến) chuyển đổi token từ định dạng trung gian (JSON) sang các định dạng đích (CSS variables, TypeScript const, Figma tokens format). Bước này nên nằm trong pipeline CI, không phải thao tác thủ công — nếu thủ công, con người sẽ quên làm, và token lại drift.
  • Ở tầng review/versioning: tokens.json nằm trong git nghĩa là mọi thay đổi token đi qua pull request, có diff rõ ràng, có thể review và revert — giống hệt cách team xử lý migration DB. Đây là lý do token nên được đối xử như một phần của "schema" hệ thống thiết kế, không phải một file thiết kế tự do chỉnh sửa không kiểm soát.
  • Ở tầng vận hành nhiều sản phẩm/brand: Nếu tổ chức có nhiều theme (light/dark, hoặc nhiều brand trong multi-tenant SaaS), kiến trúc token tách biệt "giá trị nguyên thuỷ" (primitive token, ví dụ blue-600 = #2563EB) khỏi "token ngữ nghĩa" (semantic token, ví dụ color-primary = {ref: blue-600}) cho phép đổi theme bằng cách chỉ map lại tầng semantic — không phải sửa từng component. Đây là quyết định kiến trúc ảnh hưởng trực tiếp tới cách schema theme được thiết kế trong code (ví dụ cấu trúc CSS variable theo :root[data-theme="dark"] mà nhiều codebase hiện dùng).
  • Ở tầng chi phí kỹ thuật dài hạn: Nếu không có single source of truth, mỗi lần rebrand hoặc điều chỉnh nhận diện thương hiệu sẽ tốn một dự án riêng để "grep tìm mọi màu hardcode trong code" — một công việc tốn hàng tuần và luôn sót vài chỗ. Với kiến trúc token tập trung, rebrand lý tưởng chỉ là đổi giá trị trong tokens.json rồi build lại.

Nói cách khác: quyết định "token nằm ở đâu, ai sửa, đồng bộ bằng cách nào" không phải là chi tiết vặt của design system — nó là quyết định kiến trúc dữ liệu (data architecture), tương đương việc chọn nơi đặt "nguồn sự thật" cho bất kỳ loại dữ liệu quan trọng nào trong hệ thống. Một UX Architect giỏi phải đủ hiểu kỹ thuật để đề xuất pipeline này với kỹ sư frontend, chứ không chỉ giao "file Figma đẹp" rồi để dev tự diễn giải.

Practical Example

Giả sử team đang có tình trạng: color-primary hiện là #2563EB, xuất hiện hardcode ở khoảng 40 chỗ trong code (component Button, Link, Tab active state, Progress bar...) và cũng được định nghĩa riêng trong Figma Styles.

Bước 1 — Định nghĩa token ở một nơi duy nhất, ví dụ file tokens/color.json:

{
  "color": {
    "primitive": {
      "blue-600": { "value": "#2563EB" },
      "blue-700": { "value": "#1D4ED8" }
    },
    "semantic": {
      "primary": { "value": "{color.primitive.blue-600}" },
      "primary-hover": { "value": "{color.primitive.blue-700}" }
    }
  }
}

Bước 2 — Build script sinh ra output cho từng đích dùng:

tokens/color.json
   → build:css   → styles/tokens.css     (--color-primary: #2563EB;)
   → build:figma → Figma Tokens plugin sync (Variables trong Figma)
   → build:ts    → theme.ts (export const colors = { primary: '#2563EB' })

Bước 3 — Codebase chỉ tham chiếu, không hardcode:

/* Trước — anti-pattern */
.btn-primary { background-color: #2563EB; }

/* Sau — tham chiếu token */
.btn-primary { background-color: var(--color-primary); }

Kết quả khi cần đổi màu thương hiệu: designer sửa giá trị blue-600 trong tokens/color.json (hoặc trong Figma Variables nếu quy trình cho phép sync 2 chiều), tạo pull request, review, merge. Build script chạy lại, mọi nơi dùng var(--color-primary) tự động đổi màu — không cần grep tìm từng chỗ hardcode, không cần dev "đi sửa tay" 40 file, và Figma với sản phẩm thật luôn khớp nhau vì cùng đọc từ một nguồn.

Bảng dưới minh hoạ chênh lệch effort giữa hai kiến trúc khi đổi một token màu:

Bước công việc Hai nguồn sự thật (anti-pattern) Một nguồn sự thật (token pipeline)
Đổi giá trị Sửa trong Figma Sửa 1 dòng trong tokens.json
Thông báo Nhắn Slack cho dev Không cần — PR tự nói lên thay đổi
Áp dụng vào code Dev grep + sửa tay từng file Build script tự sinh lại output
Rủi ro sót Cao (dễ quên 1-2 chỗ) Thấp (một nguồn, build toàn bộ)
Review & rollback Không có lịch sử rõ ràng Có PR diff + git history

Common Pitfalls

1. Xây design system rồi không ai maintain, khiến nó lỗi thời còn nhanh hơn cả khi không có gì. Đây là lỗi phổ biến nhất: team đầu tư 2-3 tháng xây design system hoành tráng, ra mắt rầm rộ, rồi... không ai được phân công sở hữu (own) nó lâu dài. Feature deadline dồn dập, không ai có thời gian cập nhật DS khi có thay đổi, dần dần Figma library và code phân kỳ. Một design system "chết" (outdated) còn tệ hơn không có design system, vì nó tạo ảo giác về sự nhất quán trong khi thực tế đã phân mảnh — người mới join tin tưởng nhầm vào tài liệu cũ.

2. Thiếu quy trình rõ ràng cho việc đề xuất component/pattern mới, dẫn đến mỗi người tự tạo biến thể riêng. Khi một feature team cần một biến thể button không có sẵn, nếu không có kênh đề xuất chính thức (RFC, issue template, buổi review định kỳ), designer sẽ tự tạo trong file cá nhân "cho nhanh, xin lỗi sau". Vài tháng sau có 5 biến thể button không chính thức trôi nổi khắp nơi, không cái nào được đưa vào core library, và không ai còn phân biệt được đâu là "chuẩn".

3. Nhầm lẫn giữa "có nhiều component" và "có governance tốt". Một số team đo lường sự trưởng thành của design system bằng số lượng component trong Figma, trong khi chỉ số quan trọng hơn nhiều là: tốc độ một thay đổi token lan tới sản phẩm thật là bao lâu, và tỷ lệ % màn hình thực tế trong code đang dùng đúng token/component chuẩn (thường đo bằng linting rule hoặc audit định kỳ), không phải số lượng artifact tĩnh nằm trong file thiết kế.

Checklist

  • Có tối thiểu 4 tầng rõ ràng: token → component → pattern → guideline, và mọi người trong team phân biệt được ranh giới giữa chúng
  • Token màu/spacing/typography có MỘT nguồn sự thật duy nhất (file JSON hoặc tương đương), version-controlled, có build pipeline sinh ra output cho cả Figma và codebase
  • Có mô hình quản trị bằng văn bản: ai được sửa token, ai được thêm component, quy trình đề xuất pattern mới là gì
  • Có kênh đề xuất chính thức (RFC/issue template) cho component/pattern mới, tránh tình trạng mỗi người tự chế biến thể riêng
  • Có người/nhóm sở hữu (owner) design system lâu dài, không phải dự án "làm xong rồi bỏ"
  • Có cơ chế audit định kỳ đo tỷ lệ hardcode giá trị (màu, spacing) còn sót trong codebase so với việc tham chiếu token
  • Guidelines giải thích được "khi nào dùng cái gì" bằng ngôn ngữ, không chỉ có hình ảnh component

🔗 Bài viết liên quan


Bài trước: Accessibility (a11y) · Bài tiếp theo: Atomic Design

Design System: Xây dựng hệ sinh thái, không phải chỉ là thư viện component