Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Creating a Fluid Type Scale with CSS Clamp #131

Open
AleksandrHovhannisyan opened this issue Dec 27, 2021 · 17 comments
Open

Creating a Fluid Type Scale with CSS Clamp #131

AleksandrHovhannisyan opened this issue Dec 27, 2021 · 17 comments
Labels
comments Comments section for an article.

Comments

@AleksandrHovhannisyan
Copy link
Owner

No description provided.

@AleksandrHovhannisyan AleksandrHovhannisyan added the comments Comments section for an article. label Dec 27, 2021
@solution-loisir
Copy link

First of all, thanks for such a detailed and overall high quality write up. I've been experimenting lately with fluid types and fluid design in general. Funny enough, the following idea yield some pretty good results and is less complex than what I've seen in the wild and in tutorials.

$font-h1: 2em;
$font-h2: 1.7em;
$font-h3: 1.2em;
$font-base: clamp(1.08rem , 0.46vw + 0.9rem, 1.2rem);
body {
  font-size: $font-base;
}

h1 {
  font-size: $font-h1;
}

h2 {
  font-size: $font-h2;
}

h3 {
  font-size: $font-h3;
}

Now the headings are relative to the font base which is fluid with a very slight variation. I'd like to engage discussion over such concept versus clamping everything. Have a good day!

@AleksandrHovhannisyan
Copy link
Owner Author

@solution-loisir Thanks! I actually agree—while I've found clamping to be nice for font sizing, it's a little awkward for other properties. I used to clamp my spacing variables at one point, but now I maintain a separate set of pixel measurements that I use for margins/padding/borders/etc.

Using ems like that is clever! Generally, though, it's not recommended to use ems for font sizing because it can lead to compounding effects if you apply it to deeply nested components whose parents also use em-based font sizes. (More on that here: https://www.aleksandrhovhannisyan.com/blog/use-rems-for-font-size/#a-first-attempt-with-em). This wouldn't be an issue in the code you shared because you'd never nest headings.

Personally, I also don't like the idea of tying down the font sizes to heading levels because it may lead to misuse of headings for font sizing. In my opinion, the ideal approach is to have a set of reusable font size variables and use them to style your headings by default, but then also have a separate set of font size utility classes (like .fs-sm, .fs-base, .fs-md, etc.) that can be used as needed. I do this on my site. For example, in some cases, I want to use a certain heading level for semantics but change its font size to be larger or smaller than the default.

@solution-loisir
Copy link

Thanks for the quick reply! As I read, I realized that my knowledge on em and rem was incomplete and went on a reading to clarify things up. It made me realized that I could use the same idea but with rems instead. Something like this:

$base-size: 1rem;

// Variable names and overall scaling system borrowed from Andy Bell's Gorko tool.
// https://github.com/hankchizljaw/gorko 
$size-300: $base-size * 0.8;
$size-400: $base-size * 1;
$size-500: $base-size * 1.2;
$size-600: $base-size * 1.7;
$size-700: $base-size * 2;
$size-800: $base-size * 2.4;

// Fonts
$font-base-size: clamp(#{$size-500} * 0.9, 0.46vw + 0.91rem, #{$size-500});

The base font size as to be declared on the root element though.

html {
  font-size: $font-base-size;
}

h1 {
  font-size: tokens.$size-700;
}
h2 {
  font-size: tokens.$size-600;
}
h3 {
  font-size: tokens.$size-500;
}

Now every sizes are relative to font-base-size and can be used without the 'limitations' of ems. I don't think it is a new idea. More of a fluid version of:

html {
  font-size: 1.08rem;
  @media(min-width: $md) {
    font-size: 1.2rem;
  }
}

Anyhow, this is just me tinkering on my side project. Thank again for all your input!

@AleksandrHovhannisyan
Copy link
Owner Author

AleksandrHovhannisyan commented Jun 23, 2022

Edit: Nope, this doesn't work like I thought it would since it increases the ratio at a hard breakpoint.

@solution-loisir I thought about this a bit more, and I think something like this would also work:

  --type-scale: <pick a ratio>;
  --fs-xs: calc(var(--fs-sm) / var(--type-scale));
  --fs-sm: calc(var(--fs-base) / var(--type-scale));
  --fs-base: clamp(1rem, 0.34vw + 0.91rem, 1.19rem);
  --fs-md: calc(var(--fs-base) * var(--type-scale));
  --fs-lg: calc(var(--fs-md) * var(--type-scale));
  --fs-xl: calc(var(--fs-lg) * var(--type-scale));
  --fs-2xl: calc(var(--fs-xl) * var(--type-scale));
  --fs-3xl: calc(var(--fs-2xl) * var(--type-scale));
  --fs-4xl: calc(var(--fs-3xl) * var(--type-scale));
  --fs-5xl: calc(var(--fs-4xl) * var(--type-scale));

  @media screen and (min-width: desktop) {
    --type-scale: <pick a ratio>;
  }

You could also use CSS variables to derive the base clamp so it's not hard-coded. Going to play around with this, and if it works well, I might update the post and/or https://fluid-type-scale.com/ to make this an option for the output.

@yababay
Copy link

yababay commented Jan 30, 2023

Hi, Alexandr, thank you for the great article. It became very useful for me.

It's seems there is an misprint. It should be written --font-size-sm: clamped(13.33px, 16px);... instead of --font-size-sm: clamp(13.33px, 16px);... in "Native Approach..." section.

All the best to you!

@AleksandrHovhannisyan
Copy link
Owner Author

@yababay Good catch, thanks! I'll push up a fix later today.

@NiklasGameDev
Copy link

Sorry if this a dumbass question but I have been trying to implement this into my project for days now without success.
I am no web genius at all so after reading multiple articels on fluid typography and its formulas, I am left with only one question:
Is it even possible to dynamically calculate the preffered value in regular css ?

I tried calculating all values without units and then multiplying the slope with 100vw and the intercept with 0.1rem because I am using a Rem-value of 10px. I need an explanation or my head is going to explode :(

@AleksandrHovhannisyan
Copy link
Owner Author

AleksandrHovhannisyan commented Apr 21, 2023

@NiklasGameDev Andy Bell wrote this a few years ago. It uses unitless values to ensure CSS calc works correctly for the interpolation: https://archive.hankchizljaw.com/wrote/custom-property-controlled-fluid-type-sizing/. I bet you could do something similar for clamp's min, preferred, and max values.

@yababay
Copy link

yababay commented Apr 22, 2023

Hi, NiklasGameDev, you can see working example here.

@NiklasGameDev
Copy link

I have seen this formula before in a few articles but it never worked. This one actually did work so thanks for linking it.

@bearoxo
Copy link

bearoxo commented Sep 7, 2023

Inverted min max value.

image

https://www.desmos.com/calculator/afxjle1b3d

@AleksandrHovhannisyan
Copy link
Owner Author

AleksandrHovhannisyan commented Sep 7, 2023

@bearoxo It might be worth moving that discussion to the project's repo as an issue: https://github.com/AleksandrHovhannisyan/fluid-type-scale-calculator/

That said, this is technically working as expected from a math standpoint. For example, on your graph, notice how the red curve is above the blue curve at x = -3 (corresponding to the xxs step in your screenshot above):

Comparing the curves f(x) = 161.25^x, red, and g(x) = 191.333^x, blue, at x = -3

However, I also see that comparable tools like Utopia just flip the min and max values: https://utopia.fyi/type/calculator/?c=400,16,1.25,1280,19,1.333,5,3,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12. I could do the same, but it technically wouldn't be correct, as it would mean that the minimum would be using the 1.333 type scale rather than 1.25 at that step.

@bearoxo
Copy link

bearoxo commented Sep 7, 2023

It's 2AM now and my brain is potato. But I think I get it now, that intersection point is where the fluid scaling starts working its magic, before that point it'll shrink the font size but will still max at the red curve.

@AleksandrHovhannisyan
Copy link
Owner Author

AleksandrHovhannisyan commented Sep 7, 2023

@bearoxo Correct. My tool doesn't currently warn you if you accidentally go past that intersection; I'm not sure what the level of effort would be for that. Also, as a general rule of thumb, you don't want to add too many negative steps to your type scale since the font sizes will be illegibly small (in this case, ~8px, while the minimum recommended for the web is usually 12px).

@proimage
Copy link

proimage commented Jul 17, 2024

This is a great write-up, and goes a long way to helping me figure things out. However, I've got an additional wrinkle, and I just can't figure out how to adjust the formula accordingly.

I'm working on a Tailwind site, and the client wants basically the entire site to scale up vaguely linearly with the browser size. So padding, margins, gaps, everything should scale.

They also have specific font-sizes for the smallest designed viewport (320px) and the largest one (1920px), even though the site should continue to scale beyond the largest one. Don't ask. 🤷

Anyway, to accomplish this scaling, I found that adjusting the font-size on the <html> element does the trick. So what I have now is this:

html {
	font-size: clamp(0.75rem, 0.6rem + 0.75vw, 1.5rem);
	@media (min-width: 1920px) {
		font-size: 1.25vw;
	}
}

This is a scale of 12px @ 320px wide to 24px at 1920px wide, and then just a linear scale beyond that.

I then scale the various headings with their own sizing, for example:

// H3 - 24-100px @ 320-1920w
h3 {
    font-size: clamp(2rem, 1.567rem + 3.25vw, 4.167rem);
}

The issue is that this scaling of the actual rem value makes all these clamp calculators and formulae invalid. At the lower bound, 1rem = 12px, but at the upper, it's 24px. Now, I can manually adjust the lower and upper bounds without a problem (24px when 1rem = 12px is simply 2rem, and 100px when 1rem = 24px is 4.167rem), but I can't wrap my head around the preferred value calculation.

How would I adjust the formula to account for this changing rem value?

@AleksandrHovhannisyan
Copy link
Owner Author

@proimage I don't think you can do what you're suggesting since changing the root font size changes the value of 1rem. As far as I can tell, you have two options if you want everything to scale fluidly:

  1. Set a fluid size on the root element and use rems for everything else, without clamp. So your h3 should have a font size of 2rem, for example, and that would scale up/down. I've never tried this and I suspect it won't work well but you can give it a shot.
  2. Reuse your type scale for padding/margins/gaps/etc. The "font" sizes can really be thought of as generic size tokens. This will likely run into issues with smaller values, though (e.g., in the 4-12px range).

@proimage
Copy link

proimage commented Jul 17, 2024

So, I tried putting the values in a spreadsheet and playing around with them, and then I saw how they were still just formulas, where x is the rem multiplier, and y is the vw multiplier:

12x + 3.2y = 24 // 12px base rem size @ 320px wide viewport
24x + 19.2y = 100 // 24px base rem size @ 1920px wide viewport

Being rather bad at math, I gave it to ChatGPT to try to solve, and lo and behold! It solved it like a champ:


To solve for 𝑥 and 𝑦 in the system of linear equations:

12𝑥 + 3.2𝑦 = 24
24𝑥 + 19.2𝑦 = 100

we can use the method of elimination or substitution. Here, we will use elimination:

First, simplify the equations if possible:

12𝑥 + 3.2𝑦 = 24 (1)
24𝑥 + 19.2𝑦 = 100 (2)

To eliminate 𝑥, multiply equation (1) by 2:

2(12𝑥 + 3.2𝑦) = 2(24)
24𝑥 + 6.4𝑦 = 48 (3)

Now subtract equation (3) from equation (2):

(24𝑥 + 19.2𝑦) − (24𝑥 + 6.4𝑦) = 100 − 48
24𝑥 + 19.2𝑦 − 24𝑥 − 6.4𝑦 = 52
12.8𝑦 = 52
𝑦 = 52/12.8
𝑦 = 4

Substitute 𝑦=4 back into equation (1) to solve for 𝑥:

12𝑥 + 3.2(4) = 24
12𝑥 + 12.8 = 24
12𝑥 = 24 − 12.8
12𝑥 = 11.2
𝑥 = 11.2/12

𝑥 = 11.2/12 = ~0.933 ≈ 0.933

So the solution is:

𝑥 ≈ 0.933

𝑦 = 4


It's rather hard to follow like that, so I then had it port that into formulas for the spreadsheet. It did so, and here's the link: https://docs.google.com/spreadsheets/d/1WIQHe_s1jqg3T1pY2l7wQq5Jsy2Y0EdbcXZ_cBcuhhc/edit?usp=sharing

You can copy that sheet and update the values for yourself, or perhaps press CTRL+` to see the underlying formulas; it should be pretty straightforward to port this into your calculator utility if you want.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
comments Comments section for an article.
Projects
None yet
Development

No branches or pull requests

6 participants