Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Bài 31 — Text Resize & Reflow

Đã đọc 0/14 mục · còn khoảng 12 phút
Cách chúng tôi ghi nhận

Một mục được tính là đã đọc khi nó nằm giữa màn hình đủ lâu so với độ dài của nó (bảng và sơ đồ tính một nửa), bạn vẫn đang cuộn hoặc bấm phím, và bạn đã thấy tới cuối mục. Lướt nhanh hoặc để tab mở không được tính. Chúng tôi chỉ lưu thời gian đọc từng mục — không lưu gì khác.

Mở đầu — vì sao bài này quan trọng

Hãy tưởng tượng bạn là bác Hùng, 58 tuổi, làm kế toán về hưu ở Đà Nẵng. Mắt bác đã yếu, nên trong trình duyệt bác luôn để mức zoom 150% cho dễ đọc. Một buổi sáng bác vào website ngân hàng để chuyển tiền cho con. Nhưng khi phóng to lên, phần "Số tài khoản người nhận" bị đè lên nút "Xác nhận", còn ô nhập số tiền thì bị cắt mất một nửa, muốn thấy hết phải kéo ngang qua kéo ngang lại. Bác loay hoay mười phút rồi bỏ cuộc, gọi điện nhờ con làm hộ.

Đây không phải chuyện hiếm. Theo thống kê của WHO, khoảng 2,2 tỷ người trên thế giới có vấn đề về thị lực, và phần lớn trong số đó không dùng screen reader — họ chỉ đơn giản là phóng to chữ lên. Text resize và reflow chính là hai kỹ thuật đảm bảo rằng khi người dùng phóng to nội dung, trang web của bạn vẫn dùng được, không vỡ layout, không mất chữ, không mất chức năng.

Trong bài này chúng ta sẽ đi sâu vào hai tiêu chí WCAG cực kỳ thực tế: 1.4.4 Resize Text (Level AA) và 1.4.10 Reflow (Level AA). Đây là những tiêu chí mà bất kỳ designer hay developer nào cũng có thể tự kiểm tra chỉ với trình duyệt, không cần công cụ đắt tiền. Và cũng chính vì đơn giản mà rất nhiều team bỏ qua, để rồi loại trừ hàng triệu người dùng lớn tuổi và người khiếm thị nhẹ khỏi sản phẩm của mình.

Khái niệm cốt lõi

WCAG 1.4.4 — Resize Text (Level AA)

Tiêu chí này quy định: Trừ phần chú thích (captions) và hình ảnh chứa chữ (images of text), văn bản phải có thể phóng to đến 200% mà không mất nội dung hoặc chức năng, và không cần dùng công nghệ hỗ trợ nào khác.

Nói đơn giản: khi người dùng phóng chữ lên gấp đôi, họ vẫn phải đọc được toàn bộ nội dung và bấm được tất cả các nút. Không được có chữ bị cắt cụt, không được có nút biến mất, không được có hai khối chồng lên nhau.

Con số 200% không phải ngẫu nhiên. Đây là mức phóng đại đủ để phục vụ đa số người có thị lực kém (low vision) mà không cần đến kính lúp màn hình chuyên dụng. Nếu chữ đã cần phóng hơn 200%, người dùng thường sẽ chuyển sang dùng phần mềm phóng đại màn hình (magnifier) — lúc đó là câu chuyện khác.

WCAG 1.4.10 — Reflow (Level AA)

Reflow là tiêu chí "em họ" ra đời sau (trong WCAG 2.1) và mạnh hơn. Nó yêu cầu: Nội dung phải hiển thị được mà không cần cuộn theo hai chiều (vừa dọc vừa ngang) khi hiển thị ở khung tương đương 320 CSS pixel chiều rộng.

Con số 320px này rất thông minh. Nếu bạn lấy màn hình desktop rộng 1280px và phóng to lên 400%, thì phần nội dung hiển thị thực tế chỉ còn tương đương 320px bề ngang (1280 ÷ 4 = 320). Nói cách khác, một trang đạt Reflow ở 400% zoom trên desktop cũng chính là trang responsive tốt trên điện thoại. Reflow và responsive design là hai mặt của cùng một đồng xu.

Điểm mấu chốt của Reflow: người dùng chỉ được phép cuộn theo MỘT chiều. Với nội dung đọc dọc (như tiếng Việt, tiếng Anh), họ cuộn dọc là bình thường, nhưng không được buộc phải cuộn ngang. Cuộn ngang là "kẻ thù" vì mỗi dòng chữ người dùng phải kéo qua kéo lại, cực kỳ mệt mỏi.

Phân biệt Zoom và Text-only resize

Có hai cách người dùng phóng to, và bạn cần hiểu rõ sự khác nhau:

1. Page zoom (phóng to cả trang): Đây là Ctrl + + (Windows) hoặc Cmd + + (Mac) trong trình duyệt. Nó phóng to mọi thứ — chữ, ảnh, khoảng cách — theo tỷ lệ. Đây là cách phổ biến nhất, chiếm phần lớn người dùng low vision.

2. Text-only zoom (chỉ phóng chữ): Firefox có tính năng này (View → Zoom → Zoom Text Only), và một số người dùng cài đặt cỡ chữ tối thiểu trong trình duyệt. Cách này chỉ phóng to chữ, giữ nguyên layout container. Nó khó xử lý hơn nhiều vì dễ làm chữ tràn ra khỏi khung.

Một trang web đạt chuẩn thực sự phải chịu được cả hai kiểu này.

Kẻ thù số một: đơn vị cố định

Lý do phổ biến nhất khiến trang vỡ khi phóng to là dùng đơn vị cố định (fixed) như px cho font-size, cho chiều cao container, và cho các khoảng cách. Khi bạn khóa chiều cao một cái box bằng height: 40px rồi nhét chữ vào, lúc người dùng phóng chữ lên, chữ sẽ tràn ra ngoài hoặc bị cắt. Giải pháp nằm ở việc dùng đơn vị tương đối (relative) như rem, em, %, và các kỹ thuật layout linh hoạt như Flexbox, Grid. Chúng ta sẽ nói kỹ ở phần hướng dẫn.

Tình huống thực tế

Ví dụ 1: Trang thanh toán của một sàn TMĐT Việt Nam

Một sàn thương mại điện tử lớn tại Việt Nam (gọi là "ShopViet" cho tế nhị) nhận được nhiều phản ánh từ nhóm khách hàng trên 50 tuổi rằng họ không hoàn tất được đơn hàng trên máy tính. Team QA vào cuộc và phát hiện: ở mức zoom 200%, cột tóm tắt đơn hàng (order summary) bên phải bị đẩy đè lên nút "Đặt hàng". Nút vẫn tồn tại trong DOM nhưng bị một khối div khác che mất, khiến người dùng không bấm được.

Nguyên nhân kỹ thuật: layout dùng position: absolute với tọa độ cố định bằng px cho cột summary, thay vì để nó nằm trong luồng Flexbox tự nhiên. Khi mọi thứ phóng to, khối absolute không "biết" nhường chỗ.

Sau khi chuyển sang Grid với grid-template-columns: 2fr 1fr và cho phép các cột xuống dòng (wrap) khi hẹp, tỷ lệ hoàn tất đơn (conversion) ở nhóm khách hàng lớn tuổi tăng khoảng 8%. Bài học: một lỗi accessibility nhỏ có thể là lỗ hổng doanh thu lớn, vì nhóm khách trung niên thường có sức mua cao.

Ví dụ 2: App ngân hàng và ô nhập OTP bị cắt

Một ngân hàng số ở Đông Nam Á (fintech) thiết kế màn hình nhập mã OTP với sáu ô vuông, mỗi ô width: 40px; height: 40px cố định. Trên điện thoại bình thường thì đẹp. Nhưng khi người dùng bật chế độ chữ lớn của hệ điều hành (tương đương text resize), con số trong ô phóng to trong khi ô vẫn cứng 40px, khiến chữ số bị cắt mất phần trên và phần dưới — người dùng không đọc được mình vừa gõ số mấy.

Đội thiết kế sửa bằng cách đổi kích thước ô sang min-width: 2.5rem; min-height: 2.5rem kèm padding theo em, để ô tự nở ra theo cỡ chữ. Bài học: đừng bao giờ khóa cứng chiều cao của phần tử chứa chữ. Hãy để nội dung quyết định kích thước container, không phải ngược lại.

Ví dụ 3: Trang tin tức và thanh menu ngang

Một tòa soạn báo điện tử có thanh navigation ngang với 8 mục menu, chiều rộng tính toán khít cho màn hình 1280px. Khi phóng to 200%, tám mục này không đủ chỗ, nhưng thay vì xuống dòng, chúng bị đẩy tràn ra ngoài khung, tạo thanh cuộn ngang toàn trang — vi phạm thẳng tiêu chí Reflow. Tệ hơn, ba mục cuối bị đẩy khuất khỏi màn hình và người dùng không hề biết chúng tồn tại.

Giải pháp: chuyển thanh menu thành dạng thu gọn (hamburger) khi không gian hẹp, dùng @media query dựa trên em thay vì px. Media query theo em sẽ phản ứng đúng cả khi người dùng phóng chữ, chứ không chỉ khi thay đổi kích thước cửa sổ. Bài học: Reflow không chỉ là "cho xuống dòng", mà là thiết kế lại trải nghiệm cho không gian hẹp — giống hệt tư duy mobile-first.

Hướng dẫn từng bước

Dưới đây là quy trình thực tế để đảm bảo trang của bạn vượt qua cả 1.4.4 và 1.4.10.

Bước 1 — Đặt nền bằng đơn vị tương đối. Trong CSS gốc, dùng rem cho font-size thay vì px. Ví dụ, đặt html { font-size: 100%; } (mặc định là 16px) rồi khai báo body { font-size: 1rem; }, các tiêu đề dùng h1 { font-size: 2rem; }. Khi người dùng đổi cỡ chữ mặc định của trình duyệt, mọi thứ sẽ nở đều.

Bước 2 — Đừng bao giờ đặt font-size bằng px cứng trên phần tử chứa nội dung dài. Nếu buộc phải dùng, hãy hiểu rằng px vẫn phóng theo page zoom nhưng KHÔNG phản ứng với text-only zoom hay cài đặt cỡ chữ tối thiểu. rem/em an toàn cho cả hai.

Bước 3 — Không khóa chiều cao phần tử chứa chữ. Thay height bằng min-height, và ưu tiên để padding (theo em/rem) tạo không gian. Như vậy container sẽ tự cao lên khi chữ to ra.

Bước 4 — Dùng layout linh hoạt. Thay các layout tọa độ cố định (float với width px, position absolute) bằng Flexbox hoặc CSS Grid, và bật khả năng xuống dòng: flex-wrap: wrap. Với Grid, dùng repeat(auto-fit, minmax(...)) để các cột tự sắp xếp lại.

Bước 5 — Viết media query theo em. Ví dụ @media (max-width: 40em) thay vì @media (max-width: 640px). Media query theo em sẽ kích hoạt layout thu gọn cả khi người dùng phóng chữ, đúng tinh thần Reflow.

Bước 6 — Cẩn thận với overflow: hidden và white-space: nowrap. Đây là hai thủ phạm hay che mất và cắt cụt chữ khi phóng to. Chỉ dùng khi thật sự cần và luôn kiểm tra lại ở mức zoom cao.

Bước 7 — Kiểm tra thực tế. Mở trang bằng Chrome/Firefox, nhấn Ctrl/Cmd + + cho đến 200%, rà soát toàn trang: có chữ nào bị cắt không, nút nào bị che không, có phải cuộn ngang không. Sau đó thử tiếp ở 400% để kiểm tra Reflow (tương đương khung 320px). Với Firefox, bật thêm "Zoom Text Only" để test kiểu text-only.

Bước 8 — Kiểm tra viewport meta. Đảm bảo thẻ <meta name="viewport"> KHÔNG chứa user-scalable=no hay maximum-scale=1. Những thuộc tính này chặn người dùng mobile phóng to bằng cử chỉ pinch — vi phạm accessibility trực tiếp. Chỉ nên có width=device-width, initial-scale=1.

Lỗi thường gặp & mẹo

Lỗi 1 — Chặn zoom trên mobile. Nhiều developer thêm user-scalable=no vào viewport để "giao diện trông chuyên nghiệp hơn". Đây là lỗi accessibility nghiêm trọng và phổ biến bậc nhất. Người khiếm thị nhẹ trên điện thoại hoàn toàn không thể phóng to. Hãy gỡ nó ngay.

Lỗi 2 — Khóa chiều cao nút và ô input bằng px. Như ví dụ ô OTP ở trên. Mẹo: luôn để nội dung + padding quyết định chiều cao, dùng min-height làm sàn chứ đừng dùng height làm trần.

Lỗi 3 — Dùng ảnh chứa chữ (images of text). Banner khuyến mãi với toàn bộ chữ nằm trong file ảnh sẽ vỡ nhòe khi phóng to và không thể resize như text thật. WCAG khuyến cáo tránh images of text trừ khi thật cần (logo là ngoại lệ được chấp nhận). Hãy dùng chữ HTML thật đè lên ảnh nền.

Lỗi 4 — Media query dùng px thay vì em. Trang trông responsive khi thu nhỏ cửa sổ nhưng vẫn vỡ khi phóng chữ, vì px không phản ứng với text scaling. Chuyển breakpoint quan trọng sang em.

Lỗi 5 — Quên test text-only zoom. Nhiều team chỉ test page zoom rồi tưởng đã xong. Text-only zoom (Firefox) khắc nghiệt hơn nhiều và lộ ra những chỗ container khóa cứng. Luôn test cả hai.

Mẹo vàng — Container query và clamp(). Với dự án mới, hãy tận dụng clamp() cho font-size linh hoạt (ví dụ font-size: clamp(1rem, 1rem + 0.5vw, 1.5rem)) và CSS container queries để component tự thích ứng theo không gian chứa nó, chứ không phụ thuộc kích thước viewport toàn cục. Đây là tương lai của responsive và reflow.

Mẹo — Test nhanh không cần công cụ. Chỉ cần trình duyệt và bàn phím. Đây là một trong số ít tiêu chí WCAG bạn có thể tự kiểm 100% bằng mắt trong 5 phút. Đưa nó vào checklist review mỗi PR frontend.

Bài tập thực hành

Bài 1 — Audit một trang thật. Chọn một trang bất kỳ mà bạn hay dùng (ví dụ trang chủ một ngân hàng Việt Nam hoặc một sàn TMĐT). Phóng to lên 200% rồi 400%. Ghi lại: (a) có phải cuộn ngang không, (b) có chữ nào bị cắt/che không, (c) có nút chức năng nào biến mất không. Chụp màn hình 3 lỗi tệ nhất.

Bài 2 — Sửa một component vỡ. Cho đoạn CSS sau:

.card {
  height: 120px;
  width: 300px;
  font-size: 14px;
  overflow: hidden;
}
Hãy viết lại để card này chịu được text resize 200%: đổi sang đơn vị tương đối, bỏ khóa chiều cao, và xử lý overflow an toàn. Test bằng cách phóng to trình duyệt.

Bài 3 — Chuyển breakpoint sang em. Lấy một media query px bất kỳ trong dự án của bạn, ví dụ @media (max-width: 768px), đổi sang em (chia cho 16 → 48em). So sánh hành vi khi phóng chữ trước và sau khi đổi.

Bài 4 — Kiểm tra viewport. Mở source một vài website Việt Nam, tìm thẻ <meta name="viewport">. Đếm xem bao nhiêu trang chặn zoom bằng user-scalable=no. Đây là bài tập cho bạn thấy lỗi này phổ biến đến mức nào.

Tóm tắt

Text Resize và Reflow là hai tiêu chí WCAG Level AA cực kỳ thực tế, phục vụ hàng tỷ người có thị lực kém — nhóm người dùng đông đảo hơn nhiều so với người dùng screen reader.

  • 1.4.4 Resize Text: chữ phải phóng được đến 200% mà không mất nội dung hay chức năng.
  • 1.4.10 Reflow: ở khung tương đương 320px (400% zoom trên desktop), nội dung chỉ được cuộn một chiều, không cuộn ngang.
  • Nguyên tắc nền tảng: dùng đơn vị tương đối (rem, em, %), để nội dung quyết định kích thước container, dùng Flexbox/Grid có wrap, và viết media query theo em.
  • Đừng bao giờ chặn zoom bằng user-scalable=no, khóa cứng chiều cao phần tử chứa chữ, hay dùng ảnh chứa chữ.
  • Cách test: chỉ cần trình duyệt — phóng lên 200% và 400%, kiểm tra cả page zoom lẫn text-only zoom. Đưa vào checklist review frontend.
Nhớ lại bác Hùng ở đầu bài: mục tiêu cuối cùng của chúng ta là để bác tự chuyển tiền được, không cần gọi con. Một layout linh hoạt, tôn trọng người dùng phóng to, chính là cách ta trao lại sự độc lập cho họ.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi