The principals that govern typographic design on the printed page don't always transfer to the digital screen. In web development, we don't need to worry about maximizing words per page or minimizing total page count. The economies of the printed page aren't relevant on the web. For digital developers and designers, the possibilities are nearly endless.
And therein lies the problem. Our vast range of possibilities are also available to our readers (like you, right now), who can read text on a smartphone, tablet, laptop, or an ultrawide 8K display in vertical orientation. Whereas the printed page is invariably defined (text is typeset after all), the digital viewport is a shapeshifting specter, forever haunting our designs.
How do you ensure that your text is comfortable to read on any viewport?
Thankfully, solutions exist to scale the font size of your design in relation to changes in viewport size (and I'm indebted here to posts on the subject by Geoff Graham and Michael Riethmuller). Generally speaking (and perhaps counterintuitively), font size should increase as the viewport increases, since there is more room on larger screens, and the user is likely sitting further away from it, compared to a phone.
Below I offer three solutions. Though each accomplishes the goal of increasing font size based on viewport width, I recommend the third option as being the best (it's also implemented on this website):
- Responsive Typography (easiest to get up and running)
- Fluid Typography (implements continuous font-size scaling)
- Fluid Typography via SASS mixin (easiest implementation for fluid scaling)
- Fluid Typography via JavaScript (accessibility friendly solution)
Responsive Typography
Responsive typography, which defines different font sizes based on media queries, is the easiest solution to implement and has the widest browser support. First, define your minimum desired font size (16px recommended) via CSS on your html selector. Then, increase your font size for each media query breakpoint. See the example below for an illustration.
/* Define minimum font size */
html {
font-size:16px;
}
/* Define breakpoints and font sizes */
@media (min-width:640px) and (max-width:1023px) {
html {
font-size: 17px
}
}
@media (min-width:1024px) and (max-width:1279px) {
html {
font-size: 18px
}
}
/* Define max font size and viewport */
@media (min-width:1280px) {
html {
font-size: 19px
}
}
By altering the root font size, we can use the rem CSS unit to alter the size of other typographic elements. For example, for an H1 tag with a size of 2.5rem, it would have the following sizes according to the CSS rules above.
Viewport Width | < 640 px | > 639px | > 1023px | > 1279px |
---|---|---|---|---|
Base Font Size | 16px | 17px | 18px | 19px |
H1 Font Size | 40px | 42.5px | 45px | 47.5px |
Fluid Typography
Fluid typography expands on responsive typography by implementing a formula to scale text in relation to the size of the viewport. Rather than changing font size in discrete steps according to media query breakpoints, as seen above, fluid typography sets font size along a continuum in relation to the width of the viewport.
To achieve this result, it is necessary to define a formula in CSS that is capable of dynamically updating the font-size property. The key to this formula is the CSS unit "vw" or viewport width.
The formula is as follows: min font size + (max font size - min font size) * (100vw - min vw) / (max vw - min vw)
For example, given the following arguments of 16px min font size, 23px max font size, 640px min vw, and 1440px max vw; the formula would yield the following results:
Viewport Width | Base Font Size |
---|---|
< 640px | 16px |
700px | 16.5167px |
750px | 16.95px |
1200px | 20.9px |
> 1440px | 23px |
To implement the formula in CSS, you'd write the following rules:
/* Define minimum font size */
html {
font-size: 16px;
}
/* Fluid typography formula */
@media (min-width: 640px) and (max-width: 1439px) {
font-size: calc(16px + (23 - 16) * (100vw - 640px) / (1439 - 640));
}
/* Define max font size */
@media (min-width: 1440px) {
font-size: 23px;
}
Fluid Typography via SASS Mixin
If you're using SASS/SCSS, you can implement the above solution using a mixin. The advantage of using a mixin is the ability to update variables easily and to generate the media query breakpoints automatically. The mixin and its use is shown below.
/* Define the mixin */
@mixin fluid-typography($minFont,$maxFont,$minBreakpoint,$maxBreakpoint) {
/* Define variable for media query */
$maxLessOne: $maxBreakpoint - 1;
/* Define variable for fallback */
$avg: ($maxFont + $minFont) / 2;
/* Base font size */
font-size: #{$minFont}px;
@media (min-width: #{$minBreakpoint}px) and (max-width: #{$maxLessOne}px) {
/* Adds a fallback for unsupported browsers */
font-size: #{$avg}px;
/* The fluid typography magic 🌟 */
font-size: calc(#{$minFont}px + (#{$maxFont} - #{$minFont}) * (100vw - #{$minBreakpoint}px) / (#{$maxBreakpoint} - #{$minBreakpoint}))
}
@media (min-width: #{$maxBreakpoint}px) {
font-size: #{$maxFont}px;
}
}
/* Generate the CSS */
html {
/* Just add your arguments */
@include fluid-typography(16,25,300,1500);
}
See it in action. Remember to resize your browser to see the changes 👉
Fluid Typography via JavaScript
The previous solution is limited in terms of accessibility, since setting font size in pixels will overwrite user-defined values. For example, if a user has set the font size in the browser to be 22px, the above solution would overwrite that value with 16px, which is undesirable.
Unfortunately, the way calc works in CSS doesn't allow for using a rem-based solution. You could use the responsive solution, the first example above, but then you lose fluidity. With JavaScript, though, you can preserve font-size fluidity, user-defined preferences, and aspects of your design.
The constructor defined below works by taking four arguments, the same as those above: . Then, if the user has not set a font-size preference, it implements those sizes on the DOM. If the user has set a preference, then the function implements the same same ratio you defined between the smallest and largest font sizes. For example, if you defined your smallest font size as 16px and the largest as 23px, then that represents a 43.75% increase. If a user defines a minimum font size of 22px, then the function will calculate the rem of the page based on the user supplied minimum and a 43.75% increase. For 22px, that would mean a maximum of 31.625px. The function is below.
class FluidTypography {
constructor(minVW, maxVW, minFontSize, maxFontSize) {
this.minVW = minVW;
this.maxVW = maxVW;
this.minFontSize = minFontSize;
this.maxFontSize = maxFontSize;
this.maxRem = this.computeRem().maxRem;
this.minRem = this.computeRem().minRem;
}
// Compute the maxRem based on arguments and user's browser preferences
computeRem() {
const body = document.documentElement;
const properties = window.getComputedStyle(body);
const baseFontSize = properties.fontSize.replace(/px/, '');
// Gets the max font size of either the browser or the dev
const max = Math.max(this.minFontSize, baseFontSize);
const relativeMax = (this.maxFontSize * max) / this.minFontSize;
const maxRem = relativeMax / baseFontSize;
const minRem = max / baseFontSize;
return { maxRem, minRem };
}
// Calculate font size based on arguments and user's browser preferences
fontSize() {
const width = document.documentElement.offsetWidth;
let rem = this.minRem;
if (width > this.minVW && width < this.maxVW) {
rem =
this.minRem +
((this.maxRem - this.minRem) * (width - this.minVW)) /
(this.maxVW - this.minVW);
}
if (width > this.maxVW) {
rem = this.maxRem;
}
document.documentElement.style = `font-size: ${rem}rem`;
}
resizeHandler() {
this.fontSize();
window.addEventListener('resize', this.fontSize.bind(this));
}
}
new FluidTypography(640, 1280, 17.5, 22).resizeHandler();
👉 See it in action. Remember to resize the browser to see changes.
This post first appeared on my blog
Top comments (6)
I like the idea (and hadn't thought of it) and will probably end up using this at some point...
However, isn't 'em' preferred over 'px' for fonts due to accessibility, and custom stylesheets?
You're 100% right that an accessibility best practice has been to use
em
orrem
for font size so that user-defined sizes aren't overwritten. (Custom stylesheets shouldn't be affected.).I'm doing some follow-up research to determine if this rule remains the case and how the solution above could be adapted to follow it if need be. In my testing, however, I could scale font size up and down (in Chrome desktop/mobile) and zoom without issue, but it does override default font sizes set in the browser's preferences (in Firefox). BTW, I have this technique employed on my website, if you want to see a live version.
I'll report back when I come to a conclusion regarding this technique and accessibility. If anyone else has insight on this question, please let me know!
I can't see why this wouldn't work with em (eg. 1em - 1.5em). As far as I'm aware it's still considered best practice to use em, but I'd be interested in seeing what you find.
OK, I'm back, and I don't think there's a CSS solution using just rems/ems because of how calc works and that vw is rendered in pixels. To achieve something similar, which respects any user-defined font preferences, you have to use JavaScript. I added this solution to the post.
It allows the dev to define values, including min/max breakpoints and font sizes. Those values will be used, creating the same fluid typography scale as with the SCSS solution, unless the user has defined a default font size. In that case, the function will determine the same ratio between the user-defined min font size and maximum (using rems) to maintain a similar fluid design scheme.
Nicolas Hoizey has an article about the importance of respecting default font sizes; he also uses a technique on his site for fluid typography using CSS variables, which might be appealing. Inspect the root element to see the variables.
in Fluid Typography via JavaScript at line 35
if (width > this.maxVW) { // the operator should be >=
Updated. Thank you.