Optimize Design System

Design system này là nền token dùng chung cho một financial super-app.

Nhìn lại hệ thống token cũ — token chia thành mấy tầng, đặt tên theo quy ước nào, chỗ nào còn trùng hoặc lệch. Tôi thường để tâm tới tầng này, vì muốn hệ thống đủ sức scale và đa dạng hoá trong thiết kế.

Sở dĩ tôi để tâm tới tầng này đến vậy, vì token không chỉ giúp designer dùng lại mã màu và kích thước cho các bản thiết kế nhất quán. Thứ quyết định khả năng scale ấy nằm ở tính mở rộng và kế thừa: một định nghĩa đặt đúng chỗ được cả hệ thống nhận lại mà không phải khai báo thêm lần nào. Nhờ vậy, token dần trở thành một ngôn ngữ chung — không còn là một mã màu đơn lẻ, mà là cách designer, developer, thậm chí cả BA và bộ phận nghiệp vụ cùng gọi một thứ bằng đúng một cái tên.

Design tokens · bản cũ
00. Global Colorsprimitive
brand/darkBlue · brand/red tên theo bộ nhận diện
brand/midnightBlue · brand/roseGold tên trang trí
neutral/ink · baseWhite · baseBlack
purpleHeart · lavenderMagenta
semantic/{blue, green, red, orange, yellow} “semantic” ở tầng primitive
01. Alias color tokenssemantic
background/… · text/… · border/… · icon/…
02. Component color tokenscomponent
03. Typography
04. Size — Spacing · Corner Radius · Stroke
05. Alias Corner Radius

Khả năng kế thừa ấy đến từ vị trí của token trong hệ thống: nó là tầng nền — mọi component, mọi màn hình đều đọc màu và khoảng cách từ đây, nên sửa một token là cả hệ đổi theo. Chiều ngược lại cũng đúng — một cái tên khó hiểu hay một giá trị trùng lặp sẽ âm thầm lan ra khắp nơi, và càng scale thì cái giá phải trả càng lớn. Vì thế tối ưu phải bắt đầu từ tầng token trước khi chạm tới component: dọn lớp nền cho rõ ràng thì mọi thứ phía trên tự hưởng; ngược lại, một lỗi ở tầng token sẽ lan ra theo mọi component dùng nó — càng nhiều nơi tham chiếu thì càng khó sửa, cái giá lớn dần chứ không đứng yên.

Primitive187Alias130Component243Screenshàng trăm
Đổi tên tầng nền không đổi vẻ ngoài sản phẩm — nó khiến mọi thứ phía trên đọc rõ nghĩa hơn và dễ tái dùng hơn.
Đặt tên theo vai trò

Từ tên màu thương hiệu sang tên vai trò

Nhóm màu thương hiệu trước đây được đặt theo tên bộ nhận diện cụ thể. Tên đó chỉ có nghĩa với người biết bộ nhận diện, và trói design system vào đúng một sản phẩm. Đợt này đổi sang tên theo vai trò: màu chính là primary, màu phụ là secondary, các sắc phụ trợ theo tên màu trung tính (sky, rose).

Việc đổi tên này an toàn nhờ đúng cái kiến trúc ba tầng ở trên: tầng alias trỏ về primitive bằng tham chiếu (ID), không bằng tên. Đổi tên cả tầng primitive, alias và component vẫn trỏ đúng — không vỡ một liên kết nào.

Trước Sau
  • semantic/green/50 color/green/50
    Nhãn sai tầng — màu gốc, không phải semantic
  • brand/darkBlue/60 brand/primary/60
    Tên “màu vẽ” → vai trò đã kiểm chứng
  • brand/midnightBlue/50 brand/sky/50
    Tách rõ cặp xanh gần trùng
  • brand/roseGold/40 brand/rose/40
    Bỏ tên trang trí, theo hue nhất quán
  • brand/darkBlue/60_8 brand/primary/60/a8
    Alpha khó đọc → tường minh
Cùng một giá trị màu, chỉ đổi tên — mọi binding giữ nguyên vì tham chiếu theo ID.

Cùng một liên kết, cùng một màu — nhưng một tên như brand/primary/70 nói cho bất kỳ ai biết nó là màu chính bậc 70, còn tên theo bộ nhận diện thì chỉ người trong dự án hiểu.

Trả lại nghĩa cho “semantic”

Đây là bảng màu để sáng tạo, không phải token “semantic”

Còn một điểm tinh tế hơn. Trong tầng primitive có một nhóm dải màu — xanh dương, xanh lá, đỏ, cam, vàng — bị đặt tên bắt đầu bằng semantic/. Nhưng chúng là màu thô, chưa mang vai trò gì. “Semantic” là từ dành riêng cho tầng alias, nơi token thực sự có nghĩa sử dụng như text/textError. Để một chữ mang hai nghĩa ở hai tầng khiến người đọc phân vân “cái này là primitive hay alias?“. Đợt này đổi cả nhóm sang tiền tố color/ — gọi đúng bản chất.

Đổi tên như vậy còn mở ra một giá trị ít người để ý: tự do sáng tạo. Khi được gọi là color/, nhóm này hiện ra đúng như một bảng màu đầy đủ cho designer, thay vì một hộp token kỹ thuật chỉ dành cho lập trình. Alias là nơi ràng buộc — mỗi vai trò một màu; còn color/ là nơi để thử, để phối, để tạo minh hoạ, biểu đồ, hay một trạng thái đặc biệt chưa có trong hệ thống.

Quan trọng hơn, vì mọi dải hue dùng chung một thang bậc (5, 10, 20 … 95), designer có thể chọn lọc các màu cùng chỉ số để có ngay một bộ cân bằng về sắc độ. Lấy tất cả bậc 50 qua các hue là được một palette rực cùng tông; lấy bậc 20 là được một dải pastel dịu đồng đều. Cùng chỉ số nghĩa là cùng mức tương phản — phối lên nhau không bị lệch tông.

Cùng bậc 50 qua các hue — một bộ cân bằng sẵn
blue/50
green/50
red/50
orange/50
yellow/50
purple/50
Quy ước tên

Công thức tên primitive: họ · sắc · bậc

Sau khi dọn, mọi token màu thô theo cùng một công thức đọc từ chung đến cụ thể: family / hue / step. Biến thể trong suốt thì thêm một nhánh alpha /aXX thay vì hậu tố _XX dính vào bậc.

family / hue / step        ( + /aXX cho biến thể opacity )

brand/primary/70
color/blue/60
neutral/ink/95
brand/primary/60/a8        // 8% opacity

Nhưng lợi ích lớn nhất là đồng bộ giữa thiết kế và code. Một tên có cấu trúc family/hue/step ánh xạ gần như một–một sang cách mọi UI framework khai báo token: thành CSS custom property, thành class tiện ích, hay thành một khóa trong file token chuẩn. Cùng một cái tên tồn tại ở cả Figma lẫn codebase, nên designer và developer nói chung một ngôn ngữ — không còn bước “dịch” thủ công từ màu trong Figma sang biến trong code.

// Figma variable
color/blue/60

// CSS custom property
--color-blue-60: #2e90fa;

// Utility class / token JSON
color.blue.60

Nhờ ánh xạ thẳng đó, hệ thống dễ scale: thêm một hue hay một theme mới chỉ cần khai báo đúng một lần theo công thức, rồi cả thiết kế lẫn code cùng nhận được — không nơi nào phải đặt tên lại theo quy ước riêng.

Đang làm tiếp

Những vấn đề còn mở

Đợt này mới xong tầng tên màu. Hệ thống là thứ sống, nên đây là ảnh chụp giữa hành trình — vài hướng vẫn còn để ngỏ cho những đợt sau:

  • Đưa nốt các biến thể trong suốt về cùng một cách đặt tên, để cả hệ thống thống nhất.
  • Rà lại những token đang cùng một màu và gộp về ít nguồn hơn.
  • Soát lại vài tên chưa theo đúng quy ước để bảng token đọc liền một mạch.
  • Gom những định nghĩa đang nằm rải ở hai nơi về một chỗ duy nhất.

Thứ tự thì cứ để tự nhiên: đổi tên trước vì gọn và an toàn nhờ mọi thứ trỏ bằng tham chiếu; những gì động tới sắc màu thật để sau, khi có thời gian soát kỹ. Mỗi đợt một loại thay đổi là đủ.

Một tầng nền tốt không phô trương. Người dùng không thấy nó — họ chỉ thấy mọi thứ phía trên rõ nghĩa và dễ đổi hơn.