Fluid Typography với clamp() | Type scale mượt mobile→desktop, không nhảy font đột ngột
Đừ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: Nvwphá 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ì?
| 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ự:
- Không nhỏ hơn
1.75rem - Không lớn hơn
3rem - Ở 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:
| Viewport | Font mục tiêu |
|---|---|
| 320px | 16px (= 1rem nếu root 16) |
| 1200px | 20px (= 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
| Role | Ví dụ clamp | Ý đồ |
|---|---|---|
| Body | clamp(1rem, 0.95rem + 0.25vw, 1.125rem) | Tăng nhẹ, không “phình” |
| H2 | clamp(1.5rem, 1.2rem + 1.2vw, 2.25rem) | Rõ hierarchy, có trần |
| Display / hero | clamp(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
- Min body ≥ 1rem (hoặc document rõ exception UI dense)
- Preferred có rem, không chỉ vw
- Max đủ thấp cho display trên 2K/4K
- Token đặt tên theo role (
--fs-body), không copy số magic rải rác - DevTools: resize liên tục 320→1440 — không thấy “bậc nhảy”
- Zoom 125% và 200% trên form login / nav
- Nếu card trong grid: cân nhắc
cqithayvw - 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ỗi | Triệu chứng | Cách xử |
|---|---|---|
| Quá nhiều breakpoint font | Chữ nhảy trên tablet | Một clamp / role |
clamp thiếu min | Mobile quá nhỏ | Đặt sàn rem rõ |
clamp thiếu max | Ultrawide khổng lồ | Đặt trần rem |
Preferred chỉ vw | Zoom a11y yếu | Xrem + Yvw |
| Fluid mọi thứ kể cả badge 10px | UI “nhão” | Label giữ range hẹp hoặc cố định rem |
| Match đủ 5 mốc Figma | Công thức phức tạp vô ích | Chỉ 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
- Mở Fluid Typography
- Nhập min/max viewport (px) — ví dụ 320 và 1200
- Nhập min/max font (rem) — ví dụ 1 và 1.25
- Copy CSS
clamp(...)vào:roothoặc utility class - 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 Typography Calculator — generate
clamp()từ min/max - Chuyển PX ↔ REM — chốt root trước khi ghi rem vào clamp
- Danh sách công cụ
- px vs rem trong CSS — convention root 16 vs 62.5%
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.