Is it possible to have letter-spacing relative to the font-size and inherit properly?
Asked Answered
A

3

9

My question is basically the same as this one, but replace "line-height" with "letter-spacing": When a relative line-height is inherited, it is not relative to the element's font-size. Why? And how do i make it relative?

My use case is like this:

body {
    font-size: 18px;
    letter-spacing: 1.2em; /* I would like letter-spacing to be relative to the font-size across the document, at least until I override it */
}

.small {
    font-size: 14px;
    /* letter-spacing is 1.2em of 18px instead of 14px */
}

I know that the reason it doesn't work is that the computed value, and not the specified value, is inherited, so I have to re-specify the letter-spacing every time the font-size changes. But I'm hoping there's something similar to how unitless values in line-height work.

Sure I can do this:

* {
    letter-spacing: 1.2em;
}

But then I can't stop the cascading at some element, like I would be able to with line-height:

body {
    font-size: 18px;
    line-height: 1.5;
}

.normal-line-height {
    line-height: normal;
    /* all the descendants of this element will have a normal line-height */
}

I mean, SURE, I could always do this...

.normal-letter-spacing, .normal-letter-spacing * {
    letter-spacing: normal;
}

But it's still not as elegant as I would like. I don't think there's an elegant solution to this problem, but I'm asking in case I'm missing something.

Arie answered 25/9, 2016 at 22:59 Comment(4)
maybe use media queries?Bernitabernj
@Bernitabernj I'm trying to understand how media queries could help solving this problem and I can't. Can you please elaborate?Arie
I don't think there is a way to make this more elegant... but it doesn't really matter for something simple like this. Rather than * you could set the letter spacing for each element. Elegant? No. Practical? Yes.Arrogant
@Arrogant Thank you for your input. Yes that's practical if we know all the elements under .override that will set a new font-size, but not so much if we don't. In my case, I'm working on mixins for a CSS framework, so the "relative letter-spacing" could be applied to any container.Arie
D
6

CSS variables are not widely supported but would do the trick:

body {
  font-size: 18px;
  --spacing: 1.2em;
}
.normal-letter-spacing { /* No need to select `.normal-letter-spacing *` */
  --spacing: normal;
}
body * {
  letter-spacing: var(--spacing);
}
.small {
  font-size: 14px;
}
<div>
  <p>Lorem ipsum</p>
  <p class="small">Lorem ipsum</p>
</div>
<hr />
<div class="normal-letter-spacing">
  <p>Lorem ipsum</p>
  <p class="small">Lorem ipsum</p>
</div>

They work because the value of a custom property computes to the specified value:

Computed value: specified value with variables substituted (but see prose for "invalid variables")

Therefore, unlike what happens with letter-spacing, 1.2em is not transformed to an absolute length.

Then, you can tell all elements to use --spacing as the value of letter-spacing. So 1.2em will be resolved locally with respect of the font size of each element.

Unlike * { letter-spacing: 1.2em; }, this approach sets --spacing: 1.2em only once, in the body, and lets it propagate by inheritance. Therefore, if you want to change that value in a subtree, you only need to override --spacing in the root. You don't have to select all the subtree.

Discipline answered 30/9, 2016 at 14:12 Comment(7)
"Sure I can do this: * { letter-spacing: 1.2em; }" You're just doing the same thing but moving the value to a custom property... for some reason.Leishaleishmania
I'll have to check how custom props behave with respect to inheritance, though. One sec.Leishaleishmania
@Leishaleishmania I clarified why I think this is better than * { letter-spacing: 1.2em; }.Discipline
Yeah I just remembered that custom props are inherited prior to computing their var() references. You can't actually do this without custom props, so it's not just cleaner, it's actually the only way to do it when you don't know the descendants in advance (which is the crux of the question).Leishaleishmania
A lot of people look at custom props and say "these aren't anything like Sass/LESS vars at all!" And they would be right. But that doesn't mean custom props aren't powerful. Another example.Leishaleishmania
Insane! Too bad it's not yet supported in IE/Edge.Arie
I would like to point out that it's valid to put the body * rule set above the .normal-letter-spacing one (or any future rule that changes the --spacing custom property).Arie
R
0

if you want to something relative to font height, use rem or em units.
if you want it relative to font width, use ch unit.

.foo {
  letter-spacing: .1rem;  /* 10% of the body font height */
}
.bar {
  letter-spacing: .5em;  /* 50% of the element font height */
}
.baz {
  letter-spacing: -.06ch;  /* -6% of the element font width */
}
Robbinrobbins answered 3/10, 2023 at 13:22 Comment(0)
L
-2

I would try to use REM over EM to freeze your relation with one value, which would be root (body) font-size. You wouldn't have to care about changes on font-size later.

If you'd like to use rems in more places I would advice to organise all classes in one place so you wouldn't have much problem with maintaining that. It would also be a good idea to keep it as a utility class like

.txt-spacing-big { letter-spacing: 2rem; }
.txt-spacing-medium { letter-spacing: 1rem; }
Lizzettelizzie answered 30/9, 2016 at 14:3 Comment(2)
Rems are evaluated in exactly the same way as ems, they just base off of the font-size of the root element and not the parent. Also, the root element is not body, it is html. And the entire premise of the question is that the author wants the letter-spacing to be relative to the current element's font-size, not a single constant size defined on the root, so this does not address the problem at all.Leishaleishmania
Seems I missed quite important point here. Thx for heads up @LeishaleishmaniaPickaback

© 2022 - 2024 — McMap. All rights reserved.