Mấy tuần trước mình kéo hai card bài viết từ CMS ra cùng một hàng. Card bên trái có tiêu đề ngắn ngủn, card bên phải dài gần ba dòng. Dùng clamp() thì cả hai cùng đổi theo chiều rộng khung, nhưng nó không hề biết chuỗi nào dài hay ngắn. CSS text-fit xuất hiện đúng chỗ khó chịu đó: cho trình duyệt co hoặc giãn phần chữ để khớp line box.

Nghe khá ngon, nhưng mình chưa muốn đổi CSS production chỉ vì một dòng trong release note. Mình đã dựng lab trên Chrome 150 với 12 chuỗi tiếng Việt, bốn chiều rộng và năm cấu hình. Kết quả là tính năng dùng được cho display text có kiểm soát; còn card CMS, zoom và chuỗi bất thường vẫn cần fallback tử tế.

Vì sao clamp() vẫn không giải quyết tiêu đề CMS?

clamp(), đơn vị vw hay container query đều phản ứng theo không gian. Hai tiêu đề nằm trong hai khung rộng 360px sẽ nhận cùng một cỡ chữ, dù một chuỗi chỉ có “Nghĩa Nè” và chuỗi kia là “Tiêu đề tiếng Việt dài ngắn thất thường lấy từ CMS”. Chúng rất hữu ích để đặt biên thiết kế, nhưng không đo phần glyph thực sự đang chiếm bao nhiêu chiều ngang.

.card-title {
  font-size: clamp(1.75rem, 8cqi, 3rem);
  line-height: 1.05;
  overflow-wrap: anywhere;
}

Trước text-fit, một thư viện JavaScript thường phải đọc kích thước, tính lại font, rồi ghi style mỗi khi nội dung hoặc container đổi. Explainer của nhóm Blink mô tả đúng vòng read–compute–write này và rủi ro layout thrashing. CSS native đưa phép tính vào layout engine, nhưng không có nghĩa là nó thay được mọi guardrail.

Mình vẫn giữ clamp() làm nền. Nếu trình duyệt hiểu text-fit, mình mới bật nó cho tiêu đề cần hiệu ứng. Cách suy nghĩ này giống bài dialog CSS mới: tính năng mới là progressive enhancement, không phải lý do làm browser cũ vỡ giao diện.

Môi trường và bộ tiêu đề tiếng Việt mình dùng

Mình chạy Chrome 150.0.7871.187 headless trên Windows, viewport 1440 × 900, DPR 1. Chrome trả true cho CSS.supports('text-fit: grow'). Mình không cài thêm browser hay tải font lớn chỉ để làm bài.

Thành phầnThiết lập labMục đích
Corpus12 chuỗi Việt, NFC/NFD, số tiền, tên riêng, chuỗi liềnBắt lỗi dấu, glyph và overflow
Chiều rộng240, 360, 520 và 720pxMô phỏng card đến hero
Chế độnone, grow, grow per-line-all, shrink, shrink per-line-allSo cùng một baseline
ĐoclientWidth, scrollWidth, Range rect và line rectKhông nhìn screenshot rồi đoán

Tổng cộng có 240 cấu hình ở viewport thường. Mình chạy lại cùng ma trận trong viewport CSS 720px, DPR 2 để xem bố cục khi không gian hiệu dụng giảm. Đây chỉ là mô phỏng tương đương cho audit reflow, không phải thao tác zoom 200% trên giao diện Chrome; mình ghi rõ giới hạn để khỏi biến một test gần đúng thành kết luận chắc nịch.

const range = document.createRange();
range.selectNodeContents(sample);

const result = {
  computed: getComputedStyle(sample).fontSize,
  glyphWidth: range.getBoundingClientRect().width,
  lines: [...range.getClientRects()].map(rect => rect.width),
  overflow: sample.scrollWidth > sample.clientWidth + 1,
};

Phần đo Range quan trọng hơn mình tưởng. Với text-fit, getComputedStyle().fontSize vẫn báo giá trị khai báo, nên chỉ log mỗi font-size sẽ dẫn tới kết luận sai.

Grow và shrink thực sự làm gì?

Chrome 150 release notes xác nhận bản stable ngày 30/06/2026 và mô tả text-fit là phép scale chữ theo chiều rộng container. Trong lab, chuỗi “Nghĩa Nè” ở khung có client width 488px rộng 176,36px khi để none. Bật grow consistent 180%, Range đo 317,44px: đúng mức trần 1,8 lần, không cố kéo đến đủ 488px.

Chiều ngược lại cũng vậy. Chuỗi liền 694,77px trong khung 208px chỉ giảm còn 434,22px khi dùng shrink consistent 62.5%. Nó vẫn tràn. Đây không phải bug ngẫu nhiên: 62,5% là cỡ nhỏ nhất mình cho phép, nên browser dừng để tôn trọng giới hạn đọc.

.hero-title {
  text-fit: grow consistent 180%;
}

.price-or-badge {
  white-space: nowrap;
  text-fit: shrink consistent 75%;
}
So sánh grow, shrink và fallback của CSS text-fit ở khung 240, 360 và 520 pixel
Chrome 150 phóng tiêu đề ngắn theo giới hạn 180%; chuỗi dài vẫn bị cắt khi ngưỡng shrink 62,5% chưa đủ.

Target và scale limit có cứu layout quá đà không?

CSS Text Level 5 editor’s draft ghi cú pháp gồm none | grow | shrink, target tùy chọn và phần trăm giới hạn. consistent dùng một scaling factor cho cả block; per-line xử lý từng dòng nhưng bỏ dòng cuối và forced break; per-line-all xử lý luôn các dòng đó.

Chrome 150 parse đủ các biến thể mình thử. Ở chuỗi dài trong khung 328px, per-line-all 180% đưa ba line rect đầu về khoảng 326–328px, còn dòng cuối dừng ở 154,84px vì chạm giới hạn. consistent cho hình dáng đều hơn giữa các dòng nhưng có case line rect vượt khung. Vì vậy mình không xem target là nút “tự đẹp”; designer vẫn phải xem corpus thật.

Tiếng Việt có dấu và variable font có ổn không?

Mình đưa vào các nhóm ă â ê ô ơ ư đ, dấu ngoặc kép, giá tiền, tên “Đặng Thái Sơn” và một cặp chuỗi NFC/NFD. Trên Segoe UI Variable, mình không thấy dấu bị cắt trong screenshot lab. Cặp NFC/NFD cho cùng line width ở case kiểm tra: 328px và 244,94px cho hai dòng, dù JavaScript xác nhận một chuỗi chưa normalize về NFC.

Kết quả đó chỉ nói Chrome/font trên máy mình xử lý hai cách mã hóa giống nhau trong case này. Nó không phải giấy phép bỏ qua chuẩn hóa dữ liệu CMS. Search, so sánh chuỗi, slug và bộ đếm ký tự vẫn có thể khác; normalize ở tầng dữ liệu vẫn đáng làm.

Mình cũng so Arial với Segoe UI Variable ở cùng 58px. Range của “Đặng Thái Sơn” lần lượt là 391,7px và 374px. Cùng cỡ khai báo nhưng glyph khác đã đổi chiều rộng gần 18px. Chỗ này nối khá đẹp với bài Figma Auto Layout và CSS Flexbox: handoff chỉ ổn khi font thật, nội dung thật và rule layout cùng xuất hiện trong test.

Cùng tiêu đề Đặng Thái Sơn ở Arial và Segoe UI Variable có chiều rộng glyph khác nhau
Cùng font-size 58px, Range đo Arial rộng 391,7px còn Segoe UI Variable rộng 374px trên máy mình.

Webfont tải chậm có làm chữ nhảy không?

Có khả năng, vì font fallback và font cuối có glyph metrics khác nhau. Khi font đổi, trình duyệt phải layout và fit lại. Trong lần này mình chuyển trực tiếp giữa hai font hệ thống để cô lập khác biệt metrics; mình chưa throttle một file WOFF2 qua mạng và chưa ghi CLS bằng PerformanceObserver, nên không gắn một con số CLS bịa vào bài.

Recipe mình dùng cho production là để fallback tự đứng được, rồi chỉ bật fitting khi browser hỗ trợ:

.cms-title {
  font-family: "Brand Variable", system-ui, sans-serif;
  font-size: clamp(1.75rem, 8cqi, 3rem);
  line-height: 1.08;
  overflow-wrap: anywhere;
}

@supports (text-fit: grow) {
  .cms-title[data-fit="hero"] {
    text-fit: grow consistent 160%;
  }
}

Nếu font là phần nhận diện quan trọng, preload hợp lý, subset tiếng Việt đúng và font-display phù hợp vẫn đáng làm. text-fit giảm JavaScript đo chữ; nó không làm biến mất thời gian tải font hoặc layout shift do thay bộ glyph.

Zoom 200% và cỡ chữ tối thiểu có còn đọc được?

Đây là phần khiến mình chưa dám khuyên bật đại trà. Bản nháp CSSWG ghi thẳng một vấn đề chưa có đồng thuận: khi inline size phụ thuộc viewport, kích thước biểu kiến của container có thể không đổi lúc page zoom thay đổi; text đã fit có thể không lớn lên theo zoom. Vậy nên “Chrome đã ship” không đồng nghĩa bài toán accessibility đã chốt.

Mô phỏng viewport 1440 → 720 CSS px giúp mình thấy các card fixed-width dễ làm cả trang tràn ngang. Nó không thay thế test zoom UI thật, text-only zoom hay cỡ chữ do người dùng đặt. Guardrail thực dụng của mình là không dùng text-fit cho body copy, không khóa chiều cao card, cho phép reflow và đặt scale limit bảo vệ cỡ chữ.

Ma trận guardrail cho hero, card CMS, chuỗi dài và body copy ở mức 100 và 200 phần trăm tương đương
Mô phỏng giảm viewport CSS còn một nửa nhắc mình cho phép reflow; đây không phải thao tác browser zoom thật.

Phần trăm nhỏ nhất phải được chọn từ cỡ chữ gốc. Ví dụ, base 32px với ngưỡng 75% còn 24px; base 18px với 62,5% chỉ còn 11,25px, quá dễ thành chữ trang trí khó đọc. Đời không như mơ: một chuỗi quá dài nên xuống dòng hoặc mở rộng khung, không nên bị ép đến mức người đọc phải nheo mắt.

Firefox và Safari nhận fallback nào?

Snapshot Can I Use cho text-fit mình kiểm tra ngày 02/08/2026 ghi Chrome/Edge 150 hỗ trợ, Firefox 153 và Safari 26.4 chưa hỗ trợ. Máy lần này không có Firefox và Safari nên mình không tự nhận đã test trực tiếp hai browser đó.

Điểm dễ chịu là khai báo lạ bị browser bỏ qua. Lớp nền vẫn dùng clamp(), overflow-wrap và line-height bình thường. @supports chỉ thêm fitting cho Chromium hiện tại. Nếu card cần giới hạn dòng, mình ưu tiên chỉnh nội dung hoặc layout trước; line-clamp là quyết định cắt thông tin, không phải fallback tương đương.

const canFitText = CSS.supports('text-fit: grow');
document.documentElement.classList.toggle('has-text-fit', canFitText);

Nếu cần hiệu ứng giống hệt trên mọi browser, JavaScript vẫn là phương án có chi phí. Còn nếu mục tiêu là giao diện không vỡ và Chromium được đẹp hơn, progressive enhancement nhẹ hơn nhiều. Bài Vime HTML5 Player là một ví dụ cũ về dependency UI; với bài toán nhỏ như fit headline, mình sẽ tránh kéo thêm thư viện nếu fallback CSS đã đủ đọc.

Khi nào mình dùng text-fit, khi nào không?

Use caseQuyết địnhGuardrail
Hero ngắn, nội dung biên tậpDùng grow consistentGiới hạn 140–180%, kiểm tra zoom thật
Card tiêu đề từ CMSDùng progressive enhancementKhông khóa chiều cao, có fallback xuống dòng
Giá hoặc badge một dòngCân nhắc shrinkNgưỡng nhỏ nhất và locale test rõ ràng
Caption dàiThường không dùngƯu tiên wrap tự nhiên
ButtonKhông dùng để cứu copy dàiCho nút rộng/reflow hoặc sửa nhãn
Body copyKhông dùngTôn trọng rem, zoom và cỡ chữ người dùng

Chốt lại, CSS text-fit đã hoạt động thật trên Chrome 150 và cú pháp đủ linh hoạt cho headline. Điều mình thích nhất là bỏ được vòng JavaScript đo–ghi style. Điều mình dè chừng nhất là accessibility khi zoom, font swap và cảm giác “đã fit thì chắc chắn không overflow”. Test của mình chứng minh scale limit hoàn toàn có thể để overflow tiếp tục tồn tại.

Nếu các bạn làm blog hoặc dashboard có title động, hãy bắt đầu bằng một hero ít rủi ro, mang corpus tiếng Việt thật vào test và giữ fallback đọc được. Mình sẽ chờ thêm triển khai Firefox/Safari và hướng xử lý zoom của CSSWG trước khi coi đây là mặc định. Anh em muốn xem thêm các bài giao giữa layout và typography có thể ghé chuyên mục Nghĩa làm thiết kế.

Nguồn đã kiểm tra: Chrome 150 Release Notes, ngày nguồn 30/06/2026; CSS Text Module Level 5 Editor’s Draft, ngày bản 04/06/2026; CSS fit-width text Explainer, nhánh main; web.dev — New to the web platform in June 2026, cập nhật 30/06/2026; Can I Use support table. Tất cả được kiểm tra lại ngày 02/08/2026.