Getting right to the code, here’s a working implementation:
html {
font-size: 16px;
}
@media screen and (min-width: 320px) {
html {
font-size: calc(16px + 6 * ((100vw - 320px) / 680));
}
}
@media screen and (min-width: 1000px) {
html {
font-size: 22px;
}
}
It’s worth looking at our more recent post Simplified Fluid Typography for practical, clamped, viewport-based type sizing.
That would scale font-size
from a minimum of 16px (at a 320px viewport) to a maximum of 22px (at a 1000px viewport). Here’s a demo of that, but as a Sass @mixin (which we’ll cover later).
See the Pen Base Example of Fluid Type w Sass by Chris Coyier (@chriscoyier) on CodePen.
Sass was used just to make that output a little easier to generate, and the fact that there is a smide of math involved. Let’s take a look.
Using viewport units and calc()
, we can have font-size (and other properties) adjust their size based on the size of the screen. So rather than always being the same size, or jumping from one size to the next at media queries, the size can be fluid.
Here’s the math, credit Mike Riethmuller:
body {
font-size: calc([minimum size] + ([maximum size] - [minimum size]) * ((100vw - [minimum viewport width]) / ([maximum viewport width] - [minimum viewport width])));
}
The reason that math is a little complicated is that we’re trying to avoid type ever getting smaller than our minimum or bigger than our maximum, which is very easy to do with viewport units.
For example, if we want the our font size in a range where 14px
is the minimum size at the smallest viewport width of 300px
and where 26px
is the maximum size at the largest viewport width of 1600px
, then our equation looks like this:
body {
font-size: calc(14px + (26 - 14) * ((100vw - 300px) / (1600 - 300)));
}
See the Pen JEVevK by CSS-Tricks (@css-tricks) on CodePen.
To lock in those minimum and maximum sizes, using this math within media queries helps. Here’s some Sass to help out…
In Sass
You could make a (pretty robust) mixin, like this:
@function strip-unit($value) {
@return $value / ($value * 0 + 1);
}
@mixin fluid-type($min-vw, $max-vw, $min-font-size, $max-font-size) {
$u1: unit($min-vw);
$u2: unit($max-vw);
$u3: unit($min-font-size);
$u4: unit($max-font-size);
@if $u1 == $u2 and $u1 == $u3 and $u1 == $u4 {
& {
font-size: $min-font-size;
@media screen and (min-width: $min-vw) {
font-size: calc(#{$min-font-size} + #{strip-unit($max-font-size - $min-font-size)} * ((100vw - #{$min-vw}) / #{strip-unit($max-vw - $min-vw)}));
}
@media screen and (min-width: $max-vw) {
font-size: $max-font-size;
}
}
}
}
Which you use like this:
$min_width: 320px;
$max_width: 1200px;
$min_font: 16px;
$max_font: 24px;
html {
@include fluid-type($min_width, $max_width, $min_font, $max_font);
}
Here’s another of Mike’s examples, getting fluid rhythm just right:
Extending the Idea to Headers with Modular Scale
Modular scale, meaning the more space available, the more dramatic the different in size is. Perhaps at the largest viewport with, each header up the hierarchy is 1.4x bigger than the next, but at the smallest, only 1.05x.
See view:
“Fluid Type” i̶n̶s̶p̶i̶r̶e̶d̶ stolen from @MikeRiethmuller now live on @CodePen blogs. Including “Fluid Modular Scale!” pic.twitter.com/0yJk8Iq8fR
— Chris Coyier (@chriscoyier) October 27, 2016
With our Sass mixin, that looks like:
$mod_1: 1.2; // mobile
$mod_2: 1.5; // desktop
h1 {
font-size: $mod_1*$mod_1*$mod_1*$mod_1 *1rem;
@include fluid-type($min_width, $max_width, $mod_1*$mod_1*$mod_1 *$min_font, $mod_2*$mod_2*$mod_2 *$min_font);
}
h2 {
font-size: $mod_1*$mod_1*$mod_1 *1rem;
@include fluid-type($min_width, $max_width, $mod_1*$mod_1*$mod_1 *$min_font, $mod_2*$mod_2*$mod_2 *$min_font);
}
h3 {
font-size: $mod_1*$mod_1 *1rem;
@include fluid-type($min_width, $max_width, $mod_1*$mod_1 *$min_font, $mod_2*$mod_2 *$min_font);
}
Other Reading
- Flexible typography with CSS locks by Tim Brown
- Get the Balance Right: Responsive Display Text by Richard Rutter
- Fluid type examples by Mike Riethmuller
Very nice trick! How is the browser support? IE11+?
Should work okay in IE11 and Edge:
http://caniuse.com/#feat=viewport-units
http://caniuse.com/#feat=calc
Correct me if I am wrong.
I think if you include a polyfill for viewport units and calc you can use this with any browser that the polyfill supports, right? Right?
Don’t use a polyfill. Let older browsers gracefully degrade. Adding polyfills only decreases performance, especially on older machines. The extra sizing nuance isn’t worth it.
Combine with css variables
I was looking in to integrating this in to my workflow this morning, and you left out a pretty key thing here. This fluid font size calculation should be within a media query with the minimum and maximum viewports set. Otherwise, it’ll continue scaling past the minimum and maximum. See the original post here:
https://madebymike.com.au/writing/fluid-type-calc-examples/
The Sass mixin is the whole kit-and-kaboodle that covers all that, beyond the basic concept.
prove me wrong guys, but the pen above doesn’t work in Safari?
Cheers
Thomas
I think it has to do with live complication of viewport units in Safari. Like if you refresh the page, it’s right, it’s just not fluid as you resize. Bummer. Temporary bug, I would think. Still useful.
There’s a bug in some versions of Safari (and IE11) that means it won’t be calculated on resize but as Chris said it should be correct on first load.
This covers the majority of situations. But I hear adding +100% at the end of the calculation will fix the resize bug in Safari.
After giving Chris’ shared Sass Mixin a try, it works a treat – even in Safari.
Might it be an issue in CodePen then? I mean all shared pens are struggling in Safari (which is probably the new IE?)
Just wanted to let you know and thanks for sharing this. Very cool.
Cheers
Thomas
oops – entirely missed your comment, Mike. Apologize.
Cheers
Thomas
I also noticed that in Safari calc are executed only on page reload but apparently setting font size to any vw size in a parent container fixes this and you can have fluid font size.
https://jsfiddle.net/hbyzLcj1/52/
@Konrad… Thanks for that ‘font-size: 1vw on the parent hack’. 2019 and Safari still doesn’t follow the other browsers regarding this.
Actually that doesn’t work either. It just scale down the size without the min/max size.
But I found a fix/hack for Safari that does work
add
min-height: 0vw;
on the text as wellCan anyone tell me where is the difference to vw?
This technique uses vw. Just not only vw, as then there is no way to control a minimum and a maximum or the other trickery presented here.
Thanks for the answer.
I often use vw, but on small devices the text sometimes becomes much too small. Then i correct it by using a media query for the small device and increase the font-size. And with this technique you dont need the media query, right?
Hey Chris, it’s worth sharing these examples by James Nowland: http://codepen.io/jnowland/pen/GWgbMP/ he’s made a really neat mixin and some people may prefer this syntax.
Thanks for sharing this Codepen, the Mixin is great…except for a few issues:
The line-height include doesn’t work. Chrome is saying the property value is invalid. I tried the responsive-resize Mixin as well as the responsive-type Mixin. Both don’t work for line-height…does work for font-size though.
Would love to use these Mixins:
@include responsive-type('14px@400px', '50px@1900px');
@include responsive-type('1.2@400px', '1.05@1900px', line-height);
But Sass is giving me an error when I compile: “index out of bounds for
nth($list, $n)
. Seems Sass has this funny thing where you need to add an extra comma to the end of a nested array with a single item. Can’t seem to figure out how to do that with this particular string split. Help would be appreciated.The responsive-resize Mixin works great for font-size!
I’ve been doing fluid text for the last 2.5y or so.
I use vw’s and rem’s in same CSS property block.
Even use it to treat an entire HTML object as one big design that scales.
Joe, care to hook up and exchange about this, I am also using it to style most of the elements on a page besides the typography to get a neatly flowing & fluid layout going, where can I reach you? Have some (possibly still half baked) pens and gists, perhaps you like to take a look?
Just today I actually sent an email to Mike outlining a little issue I have with the fluid type Sass mixin, that being, when using it for more than one region/media query, it does produce multiple values for the same selector, see here https://www.sassmeister.com/gist/0a451c254e43c07a7e487771d3590476 (if the CSS is not compiled straight away add a space and remove it again in the SCSS and SassMeister will re-trigger the compilation process)
I have not read it yet, the poly fluid post https://css-tricks.com/poly-fluid-sizing/ , it just hit my inbox and I am going there straight away, perhaps a solution lies there, because I think that is what I am after.
And just like Joe Hoeller writes, not only using this for typography but all styling in the dom, going from extreme 50px width to 2500px and above, great fun and once you get the hang of it you get a truly impressive and responsive layout going.
A simple to use
<a href="https://websemantics.uk/tools/responsive-font-calculator/">fluid-responsive font-size calculator</a>
:Along with a
<a href="https://codepen.io/2kool2/pen/PKGrdj">Dirty 'orrible 'acky Safari resize fix</a>
.Shameless plugs!
fluid-responsive font-size calculator
Dirty ‘orrible ‘acky Safari resize fix
@return $value / ($value * 0 + 1); Any number multiplied by 0 = 0 and 0 + 1 always = 1
@return $value / 1; Any number divided by 1 = that number
@return $value;
Or am I missing something here??
The goal of the function is to strip the units (e.g., “px”) from $value. I believe that ($value * 0) equates to 0 of whatever unit $value is in (e.g., “0px”). Then adding 1 makes it 1 of that unit (e.g., “1px”). Dividing $value (e.g., “20px”) by 1 of its units (“1px”) results in a unitless number (e.g., 20), whereas $value/1 is just $value, with units intact.
calc([minimum size] + ([maximum size] – [minimum size]) * ((100vw – [minimum viewport width]) / ([maximum viewport width] – [minimum viewport width])));
Can somebody explain the logic of build this mathematical equation ?
How we pick media query range for the math ?
I don’t understand what this is doing really? I thought you were showing us to make text expand by font-size to take up its container width. The jquery fitText() script doesn’t do that and it requires a “magic” guess coefficient that you eventually learn to tune like a guitar–something I will never be good at mind you.
After fiddling with the plugin for a while, I just wrote a quick script that does what I thought fitText would do: it resizes text to fit its parent container’s width. I didn’t throttle the resize event (or debounce or whatever) and it’s got lots of problems, but it is more in line with what I thought you were suggesting fitText does. Where did I get lost?
awesome, thank you!
I want to use the calc line in an external style sheet with WordPress.
I have to use the expression !important to overrule existing rules.
Unfortunately I couldn’t figure out where to put !important exactly.
I tried the following but the calc-line did not work:
@media screen and (min-width: 320px) {
html {
font-size: calc(16px + 6 * ((100vw – 320px) / 680))!important;
}
}
@media screen and (min-width: 1000px) {
html {
font-size: 22px!important;
}
}
Thanks for helping out!
Kind regards,
Michael
I just figured out a much shorter SASS version without the need of a mixin. I don’t set
px
in the variables. That way we can add it when needed in the font size calc instead.font-size: calc(#{$min_font * 1px} + #{$max_font - $min_font} * (100vw - #{$min_width * 1px}) / #{$max_width - $min_width});
Good article, very useful.
A question though, is it possible to use “rem” for the sizes instead?
I’ve tried it and the calculation is a bit “off”.
I try to step between 6.4rem and 3.2rem but can’t get it past “38.7968px”. Or do I have to use the “rem”-equivalent of the max/min width for it to calculate correctly?
I fixed it. The max/min-width needs to be in “rem” as well, just omit the unit.
That is an awesome solution!
Although, we had slightly different requirements for the font-scaling.
The requirement was: shrink the font-size proportional to the viewport-width so it has always the same aspect ratio and won’t cause line breaks. But only within given viewports. And only with max-font-size and min-font-size.
Your solution leads to shrinking in a slower matter than required, since a linear function between two font-sizes between two breakpoints.
Here is my approach to our requirements: https://codepen.io/shooby83/pen/JQyRao
Hopefully it’s useful for someone.
Cheers
@Matthias Schubert: well done, great approach.
Hello Chris. Fantastic code. Question: When I use this approach I find in Chrome Dev Tools the “Inherited from body” summary shows the font-size and the calc with a strikethrough even though it seems to be actually using the calc formula. Can you explain why? Thank you!
A very helpful solution!
Is there any way to do this with the font size being dynamic, like in case we accept the font size from user for an interactive page?
Hey dude!
I have to say, I am huge fan of your work, so much so I linked out to you in my latest blog post. You can check out here https://pixelzui.com/components/typography.
Cheers,
Engr. Salman Ramzan
P.S. If you share it, it would make my day.
“@if $u1 == $u2 and $u1 == $u3 and $u1 == $u4 {”
Why do all need to be of equal unit?
I can see how this is really useful for a text-heavy site (like blogs), but would you use the same approach in a web / mobile app? Seems to me, in those cases it may be overkill or even break the ui.
Any thoughts?
Did somebody tried this with custom properties and relative font units (em/rem)?
Really nice guide thanks a lot for this! I’ve just had some issues regarding ‘safari’ as I wasn’t able to resize the font ‘on the fly’. The resizing only appeared after reloading the page.
However this codepen helped me a lot by adding a ‘min-height: 0vw’ to the class. Worked for me.
That is an awesome solution!
Why don’t you use: calc(14px + (12) * ((100vw – 300px) / (1600 – 300)));
In your example:
calc(14px + (26 – 14) * ((100vw – 300px) / (1600 – 300)));
This is amazing! Super cool. Bootstrap should implement this!
I’m sure this is possible, but can this be extended to other properties like for instance the width and height of a specific div?
Ooo, that sounds like a good use case for container queries! Hopefully we’ll get those soon.
Didn’t Bootstrap integrate a fluid/responsive type mixin called RFS last year? The author wrote about it a little while back and mentioned it was planned for Bootstrap 5.
I just learned about the new-ish CSS functions
min()
,max()
, andclamp()
(clamp()
isn’t supported in Safari as I’m writing this, but the other two are, and can compose to act likeclamp()
).https://developer.mozilla.org/en-US/docs/Web/CSS/min
Seems like it enables something like this blog post suggests, just with much cleaner code!
Hi Josh,
While clamp and min-max methods aren’t as well supported as the media-query version, all these methods work in current Safari as long as you’ve used the Safari fix min-height: 0vw;
Try the test window on this page: https://websemantics.uk/tools/responsive-font-calculator/
One of the best pieces of code that I ever came upon. Works with margins, padding..well it just works and it’s awesome!! Thank you very much for this
Thank you so much for this article! No more nightmares creating responsive styles for each heading.
Very nice trick!
Although, the math is slightly complex.
Do you think
clamp()
will be a neater approach to achieve fluid typography?Again, really nice trick!
Yes!
clamp()
definitely offers a neater approach since it has minimum and maximum values baked right in. Chris walks through that in another article.Found very useful! Thanks Mr. Graham.