Text reflow on the web has an interesting relationship with Responsive Web Design. As a column gets smaller text wraps and becomes taller. But for large format display text, thatās not always what you want. What Iāve wanted for awhile now is a way to inversely size text based on the text length (where the font-size gets smaller as the heading gets longer).
Iāve been chasing the white whale of responsive text-sizing for over a decade and I think Iāve got my best attempt to-date with the new attr()
upgrades that landed in CSS (Chromium-only at time of writing).
See the Pen
inverse text-sizing with attr by Dave Rupert (@davatron5000)
on CodePen.
CSSās attr()
function got an upgrade this year where you can use it independent of content
in pseudo-elements. They also added the ability to cast types to the values. Before that change all attributes were text strings and if you tried to use it in a calc()
function, CSS would bail because you canāt multiply a pixel by a text string. With the new upgraded attr()
function you can specify a type in the new type()
function as the second parameter of attr()
: attr(foo type(<number>))
.
Having these pieces of technology in place lets us mix in HTML attributes to our CSS math.
h1 {
--ideal-cpl: 45;
--numchars: attr(data-numchars type(<number>));
--ratio: calc(var(--ideal-cpl) / var(--numchars));
--min-font-size: 1.2rem;
--max-font-size: 4rem;
font-size: clamp(
var(--min-font-size),
var(--max-font-size) * var(--ratio),
var(--max-font-size)
);
}
We start with an --ideal-cpl
, which is a typographic notion that thereās an ideal line-length at or near 66 characters per line and perhaps smaller on mobile (source: UXPin). Headings are probably less. We take that number and divide it by --numchars
from our data-numchars
attribute to get a ratio to multiply our text by (e.g. 45cpl / 60numchars = 0.75
or 45cpl / 30numchars = 1.5
).
Then set some min/max variables and chuck it all into a clamp()
function to enforce some limits. Anything under the --ideal-cpl
will clamped to the --max-font-size
and titles over the --ideal-cpl
will shrink down until they hit the --min-font-size
.
To help with graceful degradation in unsupported browsers (Safari, Firefox), we need to use the CSSOM @property
syntax to typecast our --numchars
custom property. This is a hint to browsers like Mobile Safari that --numchars
is going to be a <number>
and we can set a fallback value as well.
/* Property used to provide fallback to other browsers */
@property --numchars {
syntax: "<number>";
inherits: false;
initial-value: 45; /* set to same value as --ideal-cpl */
}
Hopefully the āmagicā here is pretty easy to understand but for completionās sake, youāll need to add data-numchars
to your HTML to make this work.
<h1 data-numchars="71">
Lorem ipsum dolor sit amet consectetur
adipisicing elit. Vitae, maxime.
</h1>
Templating languages can help. Hereās what I do in my Liquid templates for Jekyll:
<h1 data-numchars="{{ title.size }}">{{ title }}</h1>
If you donāt have access to your template but somehow have access to JavaScript, you can do something this:
document.querySelectorAll('h1').forEach(el =>
el.dataset.numchars = el.textContent.length
);
Iāve went ahead and rolled this technique out on my entire site and made a All Titles page where I can tweak variables to my liking. The result is I get to keep my chonky headlines on mobile, but almost no heading wrap more than three lines. One improvement might be to use more CSS math to snap to different short/medium/long sizes, so its less cacophonous⦠but on my site you only see one display title at a time, so itās less of a big deal.
Scaling text responsivebly
With all that in place, we have a HTML/CSS-based solution for responsive text-sizing with minimal overhead, no JS required, no extra DOM, and a graceful degradation to older browsers. I intentionally kept this dumb to allow for text wrapping, but Paul Irish forked me to make a contenteditable
demo work edge-to-edge like Kizuās Fit-to-Width or Zach Leathermanās old BigText jQuery plugin.
See the Pen
inverse text-sizing with attr - bigtext.js style - contenteditable dealio by Paul Irish (@paulirish)
on CodePen.
Impressive. Thereās lots of ways to resize text responsively and Iāve added another one to the pile, so hereās a little guide with how Iām thinking about all the options.
- If you want exact edge-to-edge text-sizing Iād probably recommend Kizuās clever Fit-to-Width technique.
- If you (and your designers) are okay with not-so-exact edge-to-edge sizing, Paulās approach seems like a good candidate.
- Donāt forget about SVG. Converting display text to outlines in Figma and exporting as SVG is also a nice option for exact fitting text one-offs, but doesnāt localize well.
- If you want text to scale-inversely to its length but still wrap, use the approach in this post.
- If you want text to be a bit more fluid between breakpoints, use
clamp()
+ vw
units.
- If you want text to scale based on itās parentās width like a vector, then DONāT USE FitText and use
clamp(var(--min-font-size), 10cqi, var(--max-font-size))
with a CSS container instead.
- If you want strict typographic control at every breakpoint, use
rem
or px
.
Lots of options for scalable type out there that give type-setting control freak nerds nightmares, have fun!