Fluid Typography với clamp() | Type scale mượt mobile→desktop, không nhảy font đột ngột

(Cập nhật: 19 tháng 7, 2026 ) CSS fluid typography clamp responsive font-size accessibility
Kết luận

Đừng ship type scale dạng bậc thang. Figma đưa 5 mốc → ai đó viết 5 @media chỉnh font-size 1–2px — giữa breakpoint chữ đứng yên, qua mốc thì nhảy. clamp(min, preferred, max) nội suy liên tục từ mobile đến desktop. Preferred nên là rem + vw (không chỉ vw), min/max bằng rem để zoom trình duyệt vẫn sống. Sinh công thức bằng Fluid Typography — chạy trên trình duyệt, không cần đăng ký.

Design handoff: body 16 / 18 / 20 / 22 / 24 tại 360, 768, 1024, 1280, “desktop large”. Junior dịch thành năm khối @media. QA trên iPad báo “chữ hơi bé”; trên ultrawide báo “heading như billboard”. Đó không phải fluid type — đó là cầu thang.

Fluid Typography dùng clamp() để font tăng mượt theo chiều rộng, không cần media query chỉ để nhích font-size. Bài này tập trung góc nhìn thực tế: mobile → desktop không nhảy font đột ngột, công thức đúng, lỗi accessibility, và khi nào vẫn giữ @media.

Bài viết này giúp bạn

  • Phân biệt bậc thang media query vs ramp clamp()
  • Đọc và viết clamp(min, preferred, max) đúng thứ tự
  • Chọn min/max viewport + rem từ design thật
  • Tránh font-size: Nvw phá zoom
  • Xây bảng type scale body / H2 / display / UI nhỏ
  • Checklist 320px · zoom 200% · 4K trước merge

Media query font vs fluid: khác gì?

Bậc thang vs ramp liên tục
Cách làm Hành vi Khi nào ổn
@media font-size Đổi tại breakpoint; giữa hai mốc đứng yên Layout đổi cột, đổi font family — không phải nhích 1px
clamp() fluid Nội suy liên tục giữa min và max Type scale product UI, landing, blog
vw thuần Scale theo viewport, dễ phá a11y Hầu như không — trừ hiệu ứng trang trí có clamp/fallback
cqi + clamp Scale theo container, không theo cả màn Card trong grid, sidebar CMS, widget dashboard

Giữ media (hoặc container) query cho: chuyển 1 cột → 2 cột, đổi line-height dense UI, swap display font, prefers-reduced-motion. Xóa query chỉ tồn tại để tăng font-size 1px tại 900px.


Đọc công thức clamp() không còn “bí ẩn”

h1 {
  font-size: clamp(1.75rem, 1.2rem + 2.5vw, 3rem);
}

Đọc theo thứ tự:

  1. Không nhỏ hơn 1.75rem
  2. Không lớn hơn 3rem
  3. Ở giữa ưu tiên 1.2rem + 2.5vw

Preferred thường là tuyến tính theo viewport. Bạn chọn hai điểm (viewport + size), giải phương trình đường thẳng — hoặc để máy tính làm.

Ví dụ ý đồ design body:

ViewportFont mục tiêu
320px16px (= 1rem nếu root 16)
1200px20px (= 1.25rem)

Kết quả gần dạng clamp(1rem, 0.93rem + 0.36vw, 1.25rem) (số exact phụ thuộc root). Luôn viết biên bằng rem để user tăng cỡ chữ mặc định vẫn có hiệu lực.

Generate nhanh trên Kawa

Fluid Typography Calculator nhận:

  • Chiều rộng viewport tối thiểu / tối đa (px)
  • Font tối thiểu / tối đa (rem)

→ Xuất clamp(...) sẵn copy. Không upload server, không tài khoản — phù hợp freelance và máy khách outsourcing khi không muốn dán token lên web lạ.


Vì sao chỉ dùng vw là footgun accessibility

/* Tránh */
p {
  font-size: 2.5vw;
}

vw thuần theo chiều rộng cửa sổ. Khi user zoom text, một số trình duyệt không tăng cỡ chữ như kỳ vọng — sàn đọc bị “kẹt” theo layout. Trong clamp(), ghép rem với phần vw giới hạn, rồi kẹp bằng min/max rem.

Test bắt buộc trước merge:

  • 320px — min còn đọc được không? (body thường ≥ 1rem)
  • Zoom 200% — heading có phá layout / đè nút không?
  • 2560px / 4K — max có chặn “billboard” không?

Một trong ba “nhìn sai” → chỉnh min/max trước khi thêm breakpoint mới.


Type scale thực dụng cho product UI

RoleVí dụ clampÝ đồ
Bodyclamp(1rem, 0.95rem + 0.25vw, 1.125rem)Tăng nhẹ, không “phình”
H2clamp(1.5rem, 1.2rem + 1.2vw, 2.25rem)Rõ hierarchy, có trần
Display / heroclamp(2rem, 1rem + 4vw, 3.5rem)Marketing only — đừng dùng cho form
Label / UI nhỏclamp(0.75rem, 0.7rem + 0.2vw, 0.875rem)Giữ chặt, tránh nhảy

Ghi chú cạnh token: min viewport, max viewport, giả định root rem. Sprint sau ai hỏi “sao H1 khác mock” sẽ không phải đoán.

:root {
  --fs-body: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
  --fs-h2: clamp(1.5rem, 1.2rem + 1.2vw, 2.25rem);
  --fs-display: clamp(2rem, 1rem + 4vw, 3.5rem);
}

body { font-size: var(--fs-body); }
h2 { font-size: var(--fs-h2); }
.hero-title { font-size: var(--fs-display); }

Fluid bổ sung modular scale (tỷ lệ body → H2 → H1), không thay thế. Giữ tỷ lệ giữa role; dùng clamp để mỗi role “thở” theo chiều rộng.


Case study: landing outsourcing bị nhảy chữ trên tablet

Bối cảnh: Team VN nhận landing từ Figma 1440. Body token: 16@375, 18@768, 20@1024, 22@1440. Dev viết bốn @media. Demo trên iPad landscape (~1180 CSS px) chữ vẫn 20px; xoay portrait (~820) rơi về 18 — QA thấy “nhảy” khi xoay máy.

Sửa: Gộp về một clamp(1rem, …, 1.375rem) với minVw 375, maxVw 1440. Xoay máy chữ tăng/giảm liên tục. Xóa ba media query chỉ phục vụ font. Giữ một query đổi grid 1→2 cột.

Bài học: Mid-size (tablet, cửa sổ split) là nơi bậc thang lộ rõ nhất — đúng chỗ fluid thắng.


Case study: hero 5vw phá zoom khách hàng lớn tuổi

Bối cảnh: Trang blog nội bộ công ty Việt. Heading font-size: 5vw. Trên desktop đẹp. User tăng cỡ chữ trình duyệt — heading gần như không đổi, body (rem) tăng → hierarchy vỡ, nút “Đọc tiếp” bị đè.

Sửa: clamp(1.75rem, 1rem + 3vw, 3rem). Zoom 200% vẫn đọc được; ultrawide không vượt 3rem.


Checklist trước khi đưa clamp vào production

  1. Min body ≥ 1rem (hoặc document rõ exception UI dense)
  2. Preferred có rem, không chỉ vw
  3. Max đủ thấp cho display trên 2K/4K
  4. Token đặt tên theo role (--fs-body), không copy số magic rải rác
  5. DevTools: resize liên tục 320→1440 — không thấy “bậc nhảy”
  6. Zoom 125% và 200% trên form login / nav
  7. Nếu card trong grid: cân nhắc cqi thay vw
  8. Copy công thức từ tool fluid typography rồi paste vào PR — kèm screenshot min/max viewport

Lỗi thường gặp

LỗiTriệu chứngCách xử
Quá nhiều breakpoint fontChữ nhảy trên tabletMột clamp / role
clamp thiếu minMobile quá nhỏĐặt sàn rem rõ
clamp thiếu maxUltrawide khổng lồĐặt trần rem
Preferred chỉ vwZoom a11y yếuXrem + Yvw
Fluid mọi thứ kể cả badge 10pxUI “nhão”Label giữ range hẹp hoặc cố định rem
Match đủ 5 mốc FigmaCông thức phức tạp vô íchChỉ lấy min + max có nghĩa

Designer đưa năm kích thước? Không bắt buộc khớp cả năm. Lấy nhỏ nhất và lớn nhất có ý nghĩa, nội suy. Điểm giữa thường là nhiễu.


Cách dùng tool trên Kawa Dev Tools

  1. Mở Fluid Typography
  2. Nhập min/max viewport (px) — ví dụ 320 và 1200
  3. Nhập min/max font (rem) — ví dụ 1 và 1.25
  4. Copy CSS clamp(...) vào :root hoặc utility class
  5. Resize DevTools + zoom để xác nhận

Phù hợp khi bạn đang standup với Figma và không muốn giải y = mx + b trên giấy.


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

clamp() có thay hoàn toàn media query không?
Không. Thay phần chỉ chỉnh font-size. Layout, mật độ, font family vẫn cần query hoặc container.

Nên viết preferred bằng calc() không?
1.2rem + 2.5vw trong clamp đã đủ ở trình duyệt hiện đại. Một số codegen cũ bọc calc() — cả hai ổn nếu số đúng.

Tailwind thì sao?
Có thể dùng arbitrary value text-[length:clamp(...)] hoặc map token CSS variable. Quan trọng là một nguồn truth cho min/max, không nhân đôi số trong config và trong Figma comment.

Modular scale (1.25, 1.333…) còn cần không?
Cần cho quan hệ giữa body/H2/H1. Clamp chỉ làm mỗi bậc đó mượt theo viewport.


Liên kết liên quan

Fluid type không phải “thêm một trick CSS”. Nó xóa cầu thang media query và bắt bạn chốt sàn, trần, độ dốc — rồi kiểm tra ở biên, không chỉ ở đúng bề rộng mock.