UX Architect

Typography & Hierarchy: Nghệ thuật điều khiển mắt người dùng bằng font chữ

7/19/2026 · 13p đọc


title: "Typography & Hierarchy: Nghệ thuật điều khiển mắt người dùng bằng font chữ"
series: "UX Architect: 40 bài Expert-Deep Dive"
part: "Phần 3 — UI Design System & Tính nhất quán"
order: 23
audience: "Product Designer & UX Architect"
reading_time: "12 phút"
tags: ["typography", "visual-hierarchy", "type-scale", "design-tokens", "design-system", "tailwind"]

Typography & Hierarchy: Nghệ thuật điều khiển mắt người dùng bằng font chữ

Mở một trang chi tiết đơn hàng lên, bạn thấy: tiêu đề "Đơn hàng #4521" chữ 18px in đậm, ngay bên dưới là dòng "Trạng thái: Đang xử lý" cũng 18px nhưng không đậm, kế tiếp là tổng tiền 24px màu xám nhạt, còn tên khách hàng lại 16px in đậm màu đen. Mắt người dùng lướt qua toàn bộ khối này trong đúng 1.5 giây đầu tiên — não không kịp phân biệt cái gì quan trọng hơn cái gì, vì kích thước và độ đậm không hề phản ánh đúng thứ tự ưu tiên thông tin. Kết quả: người dùng phải đọc lại, dừng lại, hoặc bỏ sót con số quan trọng nhất — thường là số tiền hoặc trạng thái cần hành động.

Đây không phải lỗi thẩm mỹ. Đây là lỗi kiến trúc thông tin thể hiện qua typography. Và trong phần lớn sản phẩm SaaS/CRM, lỗi này không đến từ một designer tắc trách, mà đến từ việc không có ai định nghĩa một hệ thống cỡ chữ chuẩn — mỗi màn hình, mỗi lập trình viên frontend, mỗi lần thêm tính năng mới lại tự chọn font-size theo cảm tính "nhìn cho vừa mắt". Sau vài chục sprint, sản phẩm có thể tồn tại 15-20 cỡ chữ khác nhau chỉ để thể hiện 4-5 cấp độ thông tin thực sự cần phân biệt.

Bài này đi sâu vào type scale (thang tỷ lệ cỡ chữ) như một framework bắt buộc, và quan trọng hơn — cách nó phải được hiện thực hóa như một design token sống trong codebase, chứ không phải một guideline nằm im trong file Figma không ai tuân theo.

The UX Problem

Vấn đề cụ thể: giao diện dùng quá nhiều cỡ chữ và độ đậm (font-weight) khác nhau một cách tùy hứng, khiến mắt người dùng không biết nên đọc gì trước — thông tin quan trọng nhất bị chìm nghỉm giữa những thông tin phụ.

Vì sao lỗi này phổ biến và dễ mắc phải:

  1. Typography là thứ "dễ chỉnh nhất, khó kiểm soát nhất". Không giống layout hay màu sắc — vốn có ràng buộc rõ ràng (component đã dựng sẵn, palette đã chốt) — font-size chỉ là một con số trong CSS. Bất kỳ ai, bất kỳ lúc nào, cũng có thể gõ font-size: 15px thay vì dùng token có sẵn, và không có gì ngăn cản ngay lập tức.

  2. Mỗi cỡ chữ nhìn "ổn" khi xét riêng lẻ, nhưng phá vỡ hệ thống khi đặt cạnh nhau. Designer nhìn màn hình A thấy chữ 18px đẹp, chuyển sang màn hình B lại thấy 20px đẹp hơn cho ngữ cảnh đó — nhưng khi người dùng dùng cả hai màn hình trong cùng một luồng công việc, sự thiếu nhất quán này phá vỡ cảm giác "đây là cùng một sản phẩm" và làm mắt phải học lại quy tắc phân cấp ở mỗi màn hình.

  3. Áp lực deadline khiến việc "thêm 1 style mới" luôn rẻ hơn "tìm token đã có". Lập trình viên frontend, khi cần một dòng chữ nhỉnh hơn bình thường một chút, thường viết thẳng text-[17px] (Tailwind arbitrary value) thay vì hỏi lại designer xem cấp độ này nên map vào token nào. Về ngắn hạn nhanh hơn; về dài hạn, hệ thống rã dần.

  4. Không ai "sở hữu" typography xuyên suốt sản phẩm. Design system nếu có thường tập trung vào màu sắc và spacing — phần chữ bị xem là thứ yếu, không có governance, không có ai review khi type scale bị phá vỡ.

Hậu quả kinh doanh không hề nhỏ: một dashboard tài chính nơi số liệu quan trọng (doanh thu, cảnh báo quá hạn) có cùng trọng lượng thị giác với nhãn phụ (ngày cập nhật, ghi chú nội bộ) sẽ khiến người dùng ra quyết định chậm hơn, dễ bỏ sót cảnh báo, và tăng số lần phải hỏi lại support "cái này ở đâu, số nào là số chính".

Design Framework

Framework áp dụng ở đây là Type Scale có hệ thống (systematic type scale), kết hợp với 4 biến số tạo phân cấp thị giác: size, weight, color, spacing.

1. Type scale — tỷ lệ cố định giữa các cấp độ

Một type scale là một chuỗi cỡ chữ được sinh ra từ một tỷ lệ nhân cố định (ratio), thay vì chọn số tùy ý. Ví dụ một thang phổ biến (tỷ lệ ~1.125–1.25, làm tròn cho dễ dùng):

Token Kích thước (rem/px, minh hoạ) Dùng cho
xs 0.75rem / 12px Caption, label phụ, timestamp
sm 0.875rem / 14px Body phụ, helper text, input label
base 1rem / 16px Body text mặc định
lg 1.125rem / 18px Body nhấn mạnh, sub-heading nhỏ
xl 1.25rem / 20px Heading cấp 3 (card title, section title)
2xl 1.5rem / 24px Heading cấp 2 (page section)
3xl 1.875rem / 30px Heading cấp 1 (page title)
4xl 2.25rem / 36px Hero / dashboard số liệu lớn

Điểm mấu chốt không nằm ở con số cụ thể (tỷ lệ này chỉ mang tính minh hoạ, mỗi sản phẩm có thể chọn ratio khác), mà ở nguyên tắc: số lượng cấp độ hữu hạn, và mọi nơi trong sản phẩm chỉ được chọn từ danh sách này — không có "cỡ chữ tự do" nằm ngoài thang.

2. Bốn biến số tạo phân cấp thị giác

Size không phải công cụ duy nhất — và lạm dụng size để tạo mọi cấp phân cấp sẽ khiến giao diện có quá nhiều cỡ chữ khác nhau. Nguyên tắc phân cấp nên phối hợp:

  • Size (kích thước): cấp độ càng quan trọng, size càng lớn — nhưng chỉ nên có 3-4 bước nhảy trong một màn hình bất kỳ.
  • Weight (độ đậm): regular cho body, medium/semibold cho nhấn mạnh, bold dành riêng cho điểm quan trọng nhất. Weight rẻ hơn size để tạo phân biệt vì không làm thay đổi layout nhiều.
  • Color (màu sắc/độ tương phản): text chính dùng màu tối nhất (contrast cao nhất), text phụ dùng xám nhạt hơn — đây là cách rẻ nhất để "hạ cấp" một dòng chữ mà không cần đổi size.
  • Spacing (khoảng trắng xung quanh): khoảng cách lớn hơn phía trên một heading so với phía dưới nó tạo cảm giác "nhóm" — đây là ứng dụng trực tiếp của nguyên tắc Gestalt Proximity, giúp mắt hiểu đâu là tiêu đề của khối nào.

Quy tắc vàng: một màn hình không nên cần quá 3 cấp độ heading + 2 cấp độ body để thể hiện toàn bộ phân cấp thông tin. Nếu thấy cần cấp độ thứ 4-5, thường là dấu hiệu nội dung nên được nhóm lại (progressive disclosure) chứ không phải cần thêm cỡ chữ.

The "Architect's" Insight

Đây là phần quyết định thành-bại của toàn bộ nguyên tắc trên: type scale chỉ có giá trị khi nó tồn tại như một design token — một cấu trúc dữ liệu cấu hình sống trong codebase — chứ không phải một bảng hướng dẫn nằm trong tài liệu Figma mà lập trình viên phải tự tra rồi gõ tay.

Tại sao đây là vấn đề kiến trúc, không chỉ vấn đề thẩm mỹ:

Khi type scale không được token hóa, mỗi lần một component cần chữ lớn hơn bình thường, lập trình viên viết thẳng một giá trị cụ thể vào style. Với Tailwind CSS (theo đúng convention code/fe đang dùng — Next.js 16 + Tailwind v4), điều này thể hiện qua hai thái cực:

// Cách đúng — dùng token đã định nghĩa trong theme
<h2 className="text-xl font-semibold text-gray-900">
  Chi tiết đơn hàng
</h2>

// Cách sai — arbitrary value, phá vỡ thang chuẩn
<h2 className="text-[19px] font-semibold text-gray-900">
  Chi tiết đơn hàng
</h2>

Sự khác biệt giữa text-xl (20px, một token trong theme) và text-[19px] (một giá trị tùy ý) tưởng như vô hại — chênh lệch 1px không ai nhận ra bằng mắt thường. Nhưng hệ quả kiến trúc thì rất thật:

  1. Không thể tái cấu trúc (refactor) toàn cục. Nếu một ngày công ty quyết định đổi toàn bộ heading cấp 2 từ 20px lên 22px để cải thiện readability, việc này chỉ mất 1 dòng thay đổi trong file cấu hình theme (tailwind.config hoặc CSS variables) nếu mọi nơi đều dùng token text-xl. Nhưng nếu có hàng chục chỗ dùng text-[19px], text-[20px], text-[21px] rải rác — không có cách nào tìm và sửa hết mà không grep thủ công từng file, và không thể chắc chắn đã sửa đủ.

  2. Token hóa cho phép theming và multi-brand mà không đụng vào component code. Nếu CRM có nhu cầu white-label (đổi giao diện theo từng khách hàng doanh nghiệp), hoặc chỉ đơn giản là dark mode với tỷ lệ tương phản khác, việc thay đổi giá trị của token text-base, font-semibold ở một nơi duy nhất sẽ tự động lan tỏa (cascade) ra toàn bộ hệ thống — miễn là component không tự ý hard-code giá trị.

  3. Token là hợp đồng (contract) giữa design và code — giống hệt vai trò của DTO giữa API và database. Trong quy ước dự án này, DTO validate bằng class-validator đảm bảo dữ liệu đi vào backend đúng hình dạng đã thống nhất; type scale token đóng đúng vai trò tương tự ở tầng UI — nó là "schema" cho phân cấp thị giác. Một designer thêm token mới vào file Figma mà không đồng bộ vào tailwind.config/CSS variables cũng giống như một backend dev đổi cột trong entity mà không tạo migration — trông có vẻ hoạt động ngay lập tức, nhưng phá vỡ nguồn sự thật (source of truth) và gây drift giữa các môi trường/thành viên trong đội.

  4. Component tái sử dụng phụ thuộc vào việc token nhất quán. Nếu team xây dựng một component <PageHeading> dùng chung cho toàn bộ app (theo tinh thần Atomic Design đã bàn ở bài 22), component này chỉ có thể tồn tại như một nguồn sự thật duy nhất nếu nó luôn map vào đúng 1 tổ hợp token (text-xl font-semibold). Nếu một số trang tự ý override bằng arbitrary value ngay bên trong props hoặc className ghi đè, <PageHeading> không còn ý nghĩa là "heading chuẩn" nữa — nó trở thành một cái vỏ rỗng mà ai cũng có thể phá vỡ hợp đồng bên trong.

  5. Chi phí kỹ thuật khi thang bị phá vỡ tăng phi tuyến theo quy mô. Với 5 màn hình, việc dọn dẹp type scale không nhất quán mất vài giờ. Với 200 màn hình của một CRM enterprise, cùng vấn đề này trở thành một dự án refactor riêng biệt tốn hàng tuần — vì phải review từng chỗ dùng, xác định ý đồ ban đầu của mỗi cỡ chữ tùy hứng trước khi map lại vào token đúng.

Nói cách khác: quyết định "có token hóa type scale hay không" không phải quyết định thẩm mỹ, mà là quyết định kiến trúc ảnh hưởng trực tiếp tới khả năng bảo trì (maintainability), khả năng mở rộng (scalability), và chi phí thay đổi (cost of change) của toàn bộ hệ thống frontend — giống hệt cách một quyết định index database ảnh hưởng tới hiệu năng query về lâu dài.

Practical Example

Tình huống: CRM có nhiều loại trang chi tiết (chi tiết khách hàng, chi tiết đơn hàng, chi tiết hợp đồng) — mỗi trang do một lập trình viên khác nhau xây trong các sprint khác nhau. Nếu không có token, heading của 3 trang này có thể trông như sau:

Trang Cỡ chữ heading thực tế Vấn đề
Chi tiết khách hàng 22px, font-weight 700 Do dev A tự chọn
Chi tiết đơn hàng 20px, font-weight 600 Do dev B tự chọn
Chi tiết hợp đồng 19px, font-weight 600 Do dev C copy từ trang khác rồi chỉnh tay

Người dùng chuyển qua lại giữa 3 trang này trong cùng một luồng công việc (ví dụ xử lý một deal) sẽ cảm nhận — dù không gọi tên được — rằng "hình như trang này nhìn hơi khác trang kia", làm giảm cảm giác tin cậy vào sản phẩm.

Cách khắc phục đúng — định nghĩa token trong cấu hình theme:

// tailwind.config theo hướng token hóa (minh hoạ nguyên tắc, không phải số liệu cố định)
theme: {
  extend: {
    fontSize: {
      xs:   ['0.75rem',  { lineHeight: '1rem' }],
      sm:   ['0.875rem', { lineHeight: '1.25rem' }],
      base: ['1rem',     { lineHeight: '1.5rem' }],
      lg:   ['1.125rem', { lineHeight: '1.75rem' }],
      xl:   ['1.25rem',  { lineHeight: '1.75rem' }],
      '2xl':['1.5rem',   { lineHeight: '2rem' }],
      '3xl':['1.875rem', { lineHeight: '2.25rem' }],
    }
  }
}

Và một component chuẩn dùng chung cho mọi trang chi tiết:

// components/PageHeading.tsx — Server Component, không cần "use client"
type PageHeadingProps = { children: React.ReactNode };

export function PageHeading({ children }: PageHeadingProps) {
  return (
    <h1 className="text-xl font-semibold text-gray-900 dark:text-gray-50">
      {children}
    </h1>
  );
}

Từ đó, cả 3 trang chi tiết (khách hàng, đơn hàng, hợp đồng) đều gọi <PageHeading>{title}</PageHeading> — đảm bảo heading luôn là text-xl font-semibold, bất kể ai viết code hay viết vào sprint nào. Khi cần đổi cấp độ heading toàn hệ thống, chỉ sửa 1 nơi: định nghĩa token xl trong theme hoặc chính component PageHeading.

Common Pitfalls

  1. Dùng quá nhiều font-family khác nhau trong cùng một sản phẩm. Một số trang dùng font hệ thống mặc định, trang khác designer thử nghiệm một font "đẹp hơn" tải riêng, dashboard lại dùng font thứ ba do thư viện biểu đồ tự mang theo. Hậu quả: thời gian tải trang tăng (mỗi font-family là một network request/font file riêng), và cảm giác "rời rạc" giữa các màn hình rất rõ với người dùng tinh ý dù họ không gọi tên được lý do. Nguyên tắc: tối đa 1 font-family cho toàn sản phẩm (có thể thêm 1 font monospace riêng cho dữ liệu dạng code/số liệu nếu thực sự cần).

  2. Đặt cỡ chữ theo pixel tùy hứng thay vì theo thang tỷ lệ nhất quán. Đây là lỗi đã phân tích ở trên — mỗi designer/dev "nhìn thấy đẹp thì chỉnh", dẫn đến hàng chục giá trị pixel gần giống nhau (18px, 19px, 20px, 21px) cùng tồn tại để thể hiện đúng 1 cấp độ heading. Cách phát hiện: grep toàn bộ codebase tìm các arbitrary value kiểu text-[ trong Tailwind — nếu số lượng vượt quá một ngưỡng nhỏ, đó là dấu hiệu type scale đang bị xói mòn.

  3. Dùng size để làm mọi việc, bỏ qua weight/color/spacing. Designer junior thường phản xạ "muốn nhấn mạnh → tăng size" cho mọi trường hợp, dẫn tới một màn hình có 6-7 cỡ chữ khác nhau trong khi lẽ ra chỉ cần 3 cỡ + 2 mức weight + 2 mức màu là đủ phân biệt toàn bộ thông tin. Hệ quả: thang scale bị "nới rộng" không cần thiết, và số lượng token hợp lệ trong hệ thống tăng lên mà không tăng thêm giá trị phân cấp thực sự.

Checklist

  • Đã định nghĩa type scale với số cấp độ hữu hạn (khuyến nghị 6-8 cấp: xs → 3xl/4xl), dựa trên tỷ lệ nhất quán chứ không phải số tùy chọn rời rạc.
  • Type scale tồn tại dưới dạng cấu hình trong codebase (theme config CSS utility, CSS custom properties), không chỉ nằm trong tài liệu thiết kế.
  • Toàn bộ component heading/body dùng chung đã được xây thành component tái sử dụng (PageHeading, SectionTitle, BodyText...) thay vì mỗi trang tự viết className.
  • Đã grep/lint để phát hiện và loại bỏ arbitrary font-size value (text-[Npx]) ngoài thang chuẩn; cân nhắc thêm rule lint chặn giá trị tùy ý cho font-size.
  • Toàn sản phẩm chỉ dùng tối đa 1 font-family chính (cộng 1 font monospace nếu cần cho số liệu/code).
  • Mỗi màn hình chỉ dùng tối đa 3 cấp heading + 2 cấp body — nếu cần nhiều hơn, xem lại có nên nhóm nội dung (progressive disclosure) thay vì thêm cấp độ chữ.
  • Phân cấp thị giác đã phối hợp cả 4 biến số (size, weight, color, spacing), không chỉ dựa vào size.

🔗 Bài viết liên quan

  • Atomic Design ở quy mô doanh nghiệp — vì type scale chỉ phát huy giá trị khi được đóng gói vào component atom/molecule tái sử dụng.
  • Color Theory for SaaS — màu sắc là biến số phân cấp thứ hai đi cùng typography, cùng cần được token hóa theo nguyên tắc tương tự.

Bài trước: Atomic Design · Bài tiếp theo: Color Theory for SaaS

Typography & Hierarchy: Nghệ thuật điều khiển mắt người dùng bằng font chữ