Spacing System: Tại sao 8px grid là tiêu chuẩn vàng
7/19/2026 · 13p đọc
title: "Spacing System: Tại sao 8px grid là tiêu chuẩn vàng"
series: "UX Architect: 40 bài Expert-Deep Dive"
part: "Phần 3 — UI Design System & Tính nhất quán"
order: 25
audience: "Product Designer & UX Architect"
reading_time: "12 phút"
tags: ["spacing system", "8px grid", "design tokens", "design system", "CSS utility framework", "Tailwind", "consistency"]
Spacing System: Tại sao 8px grid là tiêu chuẩn vàng
Mở Figma của một sản phẩm SaaS đã sống được ba năm, trải qua bốn designer khác nhau, không ai training ai. Chọn công cụ đo khoảng cách, rê chuột qua mười cái card trên cùng một dashboard. Kết quả: padding lần lượt là 12px, 14px, 16px, 13px, 20px. Không ai làm sai theo nghĩa "để lỗi rõ ràng" — mỗi người chỉ đơn giản kéo cho đến khi "nhìn ổn". Nhưng cộng dồn lại, giao diện có một thứ bệnh lý mà không khách hàng nào viết được thành ticket: nó "trông không chuyên nghiệp", "hơi lộn xộn dù không biết tại sao".
Đây là loại vấn đề nguy hiểm nhất trong UX — không phải bug hiển nhiên (nút bấm không chạy, form không submit), mà là một khiếm khuyết cảm nhận (perceptual defect) tích lũy từ hàng trăm quyết định nhỏ, mỗi quyết định riêng lẻ đều vô hại. Bài này mổ xẻ tại sao nguyên nhân gốc rễ là thiếu một spacing system, và tại sao con số "8" (hoặc "4") không phải mê tín của designer mà là một quyết định có hệ quả trực tiếp tới codebase.
The UX Problem
Pain point cụ thể: mỗi component trong sản phẩm được thiết kế/code độc lập theo "cảm giác nhìn ổn là được" (eyeballing) thay vì theo một quy tắc chung. Designer A làm card sản phẩm dùng padding 14px vì "nhìn cân với nội dung". Designer B làm modal dùng margin 18px vì "cần thoáng hơn một chút". Developer C, khi không có spec rõ, tự chọn margin-top: 10px cho một khoảng trống giữa hai section vì con số đó "trông được" trên màn hình của họ.
Từng quyết định, xét riêng, không sai — không ai nhìn vào một card đơn lẻ mà nói "cái padding 14px này rõ ràng là lỗi". Vấn đề chỉ lộ ra ở cấp độ hệ thống: khi người dùng lướt qua nhiều màn hình liên tiếp, não bộ của họ vô thức tổng hợp nhịp điệu khoảng cách (spacing rhythm) trên toàn sản phẩm. Khi nhịp điệu đó không nhất quán — chỗ thì khoảng cách là bội số gọn, chỗ thì lệch vài pixel — người dùng cảm nhận được sự "không đều" dù không đo được bằng mắt thường và không diễn đạt được bằng lời. Họ chỉ nói: "trông thiếu chuyên nghiệp", "trông rẻ tiền", hoặc tệ hơn, âm thầm đánh giá thấp độ tin cậy của cả sản phẩm — một hiệu ứng liên quan mật thiết đến nguyên lý thẩm mỹ-khả dụng (aesthetic-usability effect: giao diện đẹp và nhất quán được cảm nhận là dễ dùng và đáng tin hơn, bất kể chức năng thực tế).
Đây là lý do pain point này phổ biến và dễ mắc: nó không có "báo lỗi". QA không catch được nó trong test case. Nó chỉ tích lũy dần theo thời gian, đặc biệt ở những đội ngũ tăng trưởng nhanh, nhiều designer/developer làm việc song song mà không có một "nguồn sự thật" (single source of truth) cho khoảng cách.
Design Framework
Framework giải quyết vấn đề này là hệ lưới khoảng cách theo bội số cố định (spacing scale based on a fixed base unit), phổ biến nhất là bội số của 4px hoặc 8px.
Nguyên tắc cốt lõi: thay vì cho phép bất kỳ giá trị pixel nào cho margin/padding/gap, hệ thống chỉ cho phép chọn từ một tập hợp giá trị rời rạc, tất cả đều là bội số của một đơn vị cơ sở (base unit). Với base unit = 8px, thang giá trị hợp lệ thường là:
| Token | Giá trị (px) | Bội số của 8 | Use case điển hình |
|---|---|---|---|
space-0 |
0 | 0× | Reset, không khoảng cách |
space-1 |
4 | 0.5× | Khoảng cách siêu nhỏ (icon-text gap) |
space-2 |
8 | 1× | Đơn vị cơ sở — gap giữa các phần tử liên quan chặt |
space-3 |
16 | 2× | Padding trong component (card, input) |
space-4 |
24 | 3× | Khoảng cách giữa các nhóm phần tử |
space-5 |
32 | 4× | Khoảng cách giữa các section nhỏ |
space-6 |
48 | 6× | Khoảng cách giữa các section lớn |
space-7 |
64 | 8× | Khoảng cách cấp layout/page |
Tại sao 8 (chứ không phải 8 hay 10 hay số nào khác)? Ba lý do kỹ thuật cụ thể:
- Chia hết cho hầu hết kích thước màn hình và lưới phổ biến. Các độ phân giải màn hình phổ biến (375, 768, 1024, 1440, 1920px) và các hệ lưới cột phổ biến đều là bội số đẹp của 8, giúp căn chỉnh (alignment) khoảng cách với lưới bố cục (layout grid) mà không sinh ra pixel lẻ, tránh hiện tượng "nửa pixel" gây mờ viền khi render trên các mật độ điểm ảnh (density) khác nhau (1x, 2x, 3x — retina/high-DPI).
- Đủ nhỏ để linh hoạt, đủ lớn để không tạo ra quá nhiều lựa chọn. Base unit 4px cho độ mịn cao hơn (dùng khi cần khoảng cách siêu nhỏ như giữa icon và label), nhưng hệ thống chính vẫn nên neo vào bội số của 8 để giữ số lượng token ở mức quản lý được (7-8 bậc là đủ cho hầu hết sản phẩm, thay vì hàng chục giá trị pixel tùy hứng).
- Tương thích ngược với truyền thống thiết kế in ấn và lưới 12/24 cột — 8 chia hết gọn cho các bố cục lưới phổ biến, giúp spacing và grid system (Bài 11 — Information Architecture, Bài 22 — Atomic Design) nói cùng một "ngôn ngữ số học".
Việc áp dụng framework này nghĩa là: mọi khoảng cách trong sản phẩm — margin, padding, gap giữa các phần tử flexbox/grid — chỉ được chọn từ bảng token trên, không có ngoại lệ "tự do" nào. Designer không còn "cảm giác" mà chỉ còn "chọn token nào trong danh sách hữu hạn".
The "Architect's" Insight
Đây là phần quan trọng nhất, và cũng là lý do vì sao 8px grid không chỉ là một mẹo thẩm mỹ mà là một quyết định kiến trúc (architectural decision) có hệ quả trực tiếp lên codebase, design tool, và cả quy trình review giữa design và engineering.
1. Bội số cố định map tự nhiên vào thang spacing của CSS utility framework hiện đại.
Các framework CSS utility-first hiện nay (ví dụ hệ thống class dạng p-{n}, m-{n}, gap-{n} trong các framework như Tailwind) đều được xây trên một thang spacing mặc định là bội số của 0.25rem (= 4px), với các mốc phổ biến nhất — p-2, p-4, p-6, p-8 — chính là 8px, 16px, 24px, 32px. Đây không phải trùng hợp: các framework này được thiết kế theo đúng logic 4px/8px grid vì đó đã là chuẩn thực hành ngành trước khi framework ra đời.
Hệ quả thực tế: nếu design token của bạn là space-2 = 8px, space-3 = 16px, space-4 = 24px, thì developer khi implement chỉ cần map trực tiếp space-2 → p-2, space-3 → p-4, space-4 → p-6 mà không cần tính toán, không cần tra bảng quy đổi, không cần hỏi lại designer "16px này là class nào nhỉ". Ngược lại, nếu design dùng giá trị tùy hứng như 13px hay 22px, developer buộc phải viết CSS tùy chỉnh (padding: 13px inline hoặc một class custom ngoài hệ thống utility), phá vỡ tính nhất quán của toàn bộ codebase CSS và tạo ra một class "mồ côi" không nằm trong design system.
2. Spacing token là một phần của schema, không chỉ là "con số đẹp".
Trong một hệ thống design token nghiêm túc, spacing không sống trong đầu designer hay trong một file Figma cô lập — nó sống trong một file token tập trung (thường là JSON/YAML), được cả design tool và codebase cùng đọc:
{
"spacing": {
"0": { "value": "0px" },
"1": { "value": "4px" },
"2": { "value": "8px" },
"3": { "value": "16px" },
"4": { "value": "24px" },
"5": { "value": "32px" },
"6": { "value": "48px" },
"7": { "value": "64px" }
}
}
File này thường được build thành CSS custom properties (--space-2: 8px) và đồng thời cấu hình vào theme của framework CSS (ví dụ mở rộng thang spacing trong file cấu hình Tailwind). Khi base unit là bội số cố định, việc sinh mã tự động (design token → CSS variable → Tailwind config) là một phép nhân đơn giản, không có làm tròn, không có sai số. Khi base unit KHÔNG phải bội số cố định (ví dụ ai đó chèn một giá trị 13px vào giữa thang), pipeline sinh token tự động sẽ hỏng — hoặc phải hard-code ngoại lệ, làm token system mất tính "nguồn sự thật duy nhất" (single source of truth).
3. Ảnh hưởng tới component API và tính tái sử dụng ở tầng code.
Khi spacing được chuẩn hóa thành token hữu hạn, các component trong thư viện (Button, Card, Modal — xem thêm Bài 22 về Atomic Design) có thể expose prop dạng enum thay vì raw pixel:
type SpacingToken = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
interface StackProps {
gap: SpacingToken; // chỉ nhận token, KHÔNG nhận number tùy ý
padding?: SpacingToken;
}
Điều này chặn từ gốc rễ việc một developer vô tình viết gap: 13 hoặc padding: '18px' trực tiếp trong component — TypeScript sẽ báo lỗi type ngay từ lúc code, biến "quy ước thiết kế" (design convention, vốn dễ bị phá vỡ vì không có ràng buộc kỹ thuật) thành "ràng buộc kiểu dữ liệu" (type constraint, được compiler enforce). Đây chính là điểm một UX Architect khác biệt với designer thuần thẩm mỹ: quyết định "dùng bội số 8" không dừng ở Figma, nó lan tới enum trong type system, tới cấu hình theme, tới cả linter rule (có thể viết rule cấm giá trị spacing không nằm trong whitelist).
4. Chi phí kỹ thuật khi vi phạm nguyên tắc lan rộng theo cấp số nhân.
Một giá trị spacing "lệch chuẩn" khi component đó được tái sử dụng ở 20 nơi trong sản phẩm sẽ nhân bản thành 20 điểm không nhất quán. Vì component trong kiến trúc frontend hiện đại (React/Vue component tree) được compose và tái sử dụng liên tục, một quyết định sai ở tầng token lan truyền theo đúng cấu trúc dependency graph của ứng dụng — không dừng ở một màn hình. Đây là lý do sửa spacing "về sau" luôn đắt hơn nhiều so với chuẩn hóa "từ đầu": sửa một token nghĩa là phải audit lại toàn bộ nơi dùng token đó (hoặc tệ hơn, audit từng giá trị hard-code rải rác không theo token nào).
Practical Example
Tình huống: một danh sách (list) hiển thị các bản ghi khách hàng trong module CRM, mỗi item là một row có avatar, tên, trạng thái, và nút hành động.
Trước khi có spacing system (mỗi developer tự quyết theo cảm giác):
.customer-row {
padding: 14px 18px;
margin-bottom: 10px;
}
.customer-row .avatar {
margin-right: 13px;
}
.customer-row .status-badge {
margin-left: 9px;
}
Ba giá trị margin/padding khác nhau (14, 18, 10, 13, 9) không có mối quan hệ toán học nào với nhau — không designer nào nhớ được lý do tại sao là 13 chứ không phải 12 hay 14, và không developer nào có căn cứ để refactor lại mà không phải hỏi lại design.
Sau khi áp dụng spacing token bội số 8:
.customer-row {
padding: var(--space-3) var(--space-4); /* 16px 24px */
margin-bottom: var(--space-2); /* 8px */
}
.customer-row .avatar {
margin-right: var(--space-3); /* 16px */
}
.customer-row .status-badge {
margin-left: var(--space-2); /* 8px */
}
Toàn bộ khoảng cách trong component giờ chỉ dùng 3 token (space-2, space-3, space-4). Khi designer thiết kế một component tương tự khác (ví dụ danh sách đơn hàng), họ không cần đo lại từ đầu — họ tra bảng token và biết ngay "khoảng cách giữa avatar và tên luôn là space-3" ở mọi nơi trong sản phẩm. Developer, khi review pull request, có thể viết linter rule chặn thẳng bất kỳ giá trị px nào không map vào biến --space-*, biến việc tuân thủ spacing system từ "nhắc nhở bằng lời" thành "chặn tự động ở CI".
Common Pitfalls
1. "Chỉ phá lệ một lần thôi" — rồi ngoại lệ trở thành tiền lệ. Một designer gặp trường hợp đặc biệt (ví dụ một banner quảng cáo cần margin 20px để "vừa khít" với hình ảnh) và tự cho phép ngoại lệ "chỉ lần này". Vấn đề là: ngoại lệ đầu tiên luôn tạo tiền lệ cho ngoại lệ thứ hai ("trước đây từng có ngoại lệ rồi mà"), và sau vài tháng, hệ thống token trở thành "gợi ý" thay vì "quy tắc", quay lại đúng tình trạng hỗn loạn ban đầu. Kỷ luật đúng là: nếu một use case thực sự cần giá trị ngoài thang, đó là tín hiệu cần mở rộng thang token (thêm một bậc mới, có tên, có lý do, được cả team biết) — không phải viết một giá trị rời rạc chỉ dùng một lần.
2. Không đồng bộ bội số giữa design tool và codebase. Đây là lỗi âm thầm nguy hiểm nhất: designer dùng grid 8px trong Figma (padding component đo được là bội số 8 khi kiểm tra), nhưng theme cấu hình trong code lại dùng thang khác (ví dụ thang mặc định bội số của rem không đúng ăn khớp, hoặc một theme cũ để lại từ trước khi chuẩn hóa). Kết quả: designer nghĩ mình đang tuân thủ 8px grid, nhưng khi code build ra, con số thực tế trên trình duyệt lệch vài pixel so với thiết kế do làm tròn rem-to-px khác nhau giữa các trình duyệt, hoặc do developer chọn nhầm token liền kề. Cách phòng tránh: token phải là một file nguồn duy nhất (design token file) được cả Figma (qua plugin đồng bộ token) và codebase (qua build pipeline sinh CSS variable/theme config) cùng đọc — không phải hai người tự gõ tay hai nơi.
3. Nhầm lẫn giữa spacing token và kích thước component. Một lỗi junior hay gặp: dùng spacing token để định nghĩa cả kích thước cố định của icon hay avatar (ví dụ "avatar to = space-6"), trộn lẫn hai khái niệm khác nhau — spacing (khoảng cách giữa các phần tử) và sizing (kích thước bản thân phần tử). Khi hai hệ thống này trộn vào nhau, thay đổi thang spacing (ví dụ thêm bậc mới cho responsive) vô tình làm vỡ luôn kích thước icon ở khắp nơi. Nguyên tắc đúng: spacing scale và sizing scale nên là hai token namespace tách biệt, dù có thể chia sẻ cùng base unit.
Checklist
- Đã chọn base unit duy nhất (4px hoặc 8px) và ghi rõ lý do lựa chọn trong tài liệu design system.
- Thang spacing token có số bậc hữu hạn (khuyến nghị 6-8 bậc), mỗi bậc có tên rõ ràng (
space-1,space-2...), không cho phép giá trị pixel tự do. - Token spacing được lưu ở một file nguồn duy nhất (JSON/YAML), sinh ra cả cấu hình Figma (qua plugin token) lẫn CSS variable/theme config trong codebase — không gõ tay hai nơi.
- Component library expose prop spacing dạng enum/token (không nhận raw number), được type system enforce.
- Có linter/lint rule (CI) chặn giá trị margin/padding/gap hard-code không thuộc whitelist token.
- Mọi ngoại lệ "cần giá trị ngoài thang" được xử lý bằng cách đề xuất mở rộng thang chính thức, có review, không tự ý viết giá trị rời rạc.
- Có tài liệu ánh xạ rõ ràng giữa từng token và use case điển hình (ví dụ
space-2= gap giữa phần tử liên quan chặt,space-5= khoảng cách giữa section) để designer mới không phải đoán.
🔗 Bài viết liên quan
- Atomic Design: Áp dụng ở quy mô doanh nghiệp — spacing token là một trong những "nguyên tử" nền tảng cấu thành component ở mọi cấp atomic.
- Design System: Xây hệ sinh thái, không chỉ bộ UI kit — spacing system là một lát cắt cụ thể của tư duy hệ sinh thái token được bàn ở bài này.
Bài trước: Color Theory for SaaS · Bài tiếp theo: Responsive Design