Fluid Font Size by Character Count
I recently had a use case where CMS provided headlines needed to reasonably fit into a well defined space within a hero section. The hope was to fill that space a bit better by using larger font sizes for short headlines, and smaller sizes for longer ones, all based on character length.
The Result#anchor
Putting the end result up here at the top because I know you’re pressed for time and just want Something That Works™️
See the Pen Length-dependent Font Size by Steve Woodson (@stevenwoodson) on CodePen.
The part you want to copy is the JavaScript, the CSS is mostly just for illustrative purposes as is most of the HTML. Here’s how to use it:
- Add the JavaScript to your page
- Change any of the six variables (
minFontSize
,maxFontSize
,minLineLength
,maxLineLength
,minViewportWidth
,maxViewportWidth
) to fit your needs. - Add
data-dynamic-font-size
to any headline you want to have this fluid size
How it Works#anchor
The TL;DR is “with CSS Clamp and a bit of JavaScript”, read on for something a bit more descriptive.
The Variables#anchor
We need a general range of characters that should affect the font sizing. This could vary based on the amount of space you have available. These are defined in minLineLength
and maxLineLength
.
We also need to know how big (and small) the text should be allowed to get. These are defined (in pixels) in minFontSize
and maxFontSize
.
Finally, we need to know the range of browser widths that should affect the calculations. These are also in pixel values and are defined in minViewportWidth
and maxViewportWidth
.
Relative Max Size#anchor
The first thing we need is a character count of the text in the container we’re referencing. This is achieved by the line dynamicFontEl.textContent.replace(/(<([^>]+)>)/gi, "").trim().length
towards the bottom. That’s stripping out any HTML tags and then trimming before getting a string count.
With the variables and the total number of characters defined, the first calculation is to determine the ideal max font size for that character length, this is gathered in relativeMaxFontSize
. The fewer characters there are, the more space there is available to make the font size bigger.
const relativeMaxFontSize =
textLength < minLineLength
? maxFontSize
: textLength > maxLineLength
? minFontSize
: maxFontSize -
((maxFontSize - minFontSize) * (textLength - minLineLength)) /
(maxLineLength - minLineLength);
Some examples to illustrate:
- If the text is less than or equal to
minLineLength
, the font size will bemaxFontSize
- If the text is in between the min and max line length, we calculate where in that range it is and use the same range percentage for the font size. So a line length that is right in the middle of the min and max would lead to a font size right in the middle too.
- If the text is greater than or equal to
maxLineLength
, the font size will beminFontSize
Introducing Clamp#anchor
We could stick with just the calculation above, set that resulting relativeMaxFontSize
to be the font size of the container and call it a day. But, I wanted to make use of fluid typography so the text would size appropriately on smaller viewports too. This is where good old CSS clamp comes in.
First we calculate a subsection of the viewport width range set in minViewportWidth
and maxViewportWidth
relative to the max size we calculated before. We’re doing this because we don’t need the smallest character length headlines to size down until much smaller than the rest. Less characters means it can stick around at the larger size for longer without unwanted line breaks.
// Relative max viewport width, gets smaller the bigger the font size so it doesn't resize too soon
const relativeMaxViewportWidth =
maxViewportWidth * (minFontSize / relativeMaxFontSize);
// Relative min viewport width, gets bigger the smaller the font size so it doesn't resize too late
const relativeMinViewportWidth =
minViewportWidth * (maxFontSize / relativeMaxFontSize);
// Viewport width calculations
const viewportWidth =
(100 * (maxFontSize - minFontSize)) /
(relativeMaxViewportWidth - relativeMinViewportWidth);
Then, we get the viewportWidth
and relativeFontSize
to use in the clamp(). These are using the relative viewport sizes to adjust the font size at the right viewport size for that character length.
// Viewport width calculations
const viewportWidth =
(100 * (maxFontSize - minFontSize)) /
(relativeMaxViewportWidth - relativeMinViewportWidth);
// Relative font size calculation
const relativeFontSize =
(relativeMinViewportWidth * maxFontSize -
relativeMaxViewportWidth * minFontSize) /
(relativeMinViewportWidth - relativeMaxViewportWidth);
Finally, we’re ready to pull together the final clamp values!
The clamp min is always going to be minFontSize
represented in rem
values, the clamp max value uses relativeMaxFontSize
, because we want to make sure it’s not allowed to ever get any bigger than that. The current font size uses all that calculation magic above and is set to ${viewportWidth}vw + ${relativeFontSize / 16}rem
.
The End Result#anchor
What we’re left with is a calculated font size that is bigger the shorter the text gets, and that font size fluidly resizes based on the viewport width at the ideal time for the size and space available.
The best way to get a better sense for how this works is to open up that CodePen shared above in a new tab and resize the browser. You’ll notice that the last (longest and smallest) headline doesn’t resize at all, and each subsequent headline going up will start to resize one after the other.
All filling the available space as well as they possibly could. Pretty cool, right?
References#anchor
I’m always big on giving credit to the posts and projects that inspired me, here’s some for this work in no particular order:
Need help with an upcoming project?
I'd love to hear more about what you have coming up and how I can help bring it to life! My earliest availability is currently Q1 2025.
Get in Touch