Skip to content

[css-size-adjust] Clarify interaction between text-size-adjust and em units #11636

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

Open
progers opened this issue Feb 3, 2025 · 1 comment
Open

Comments

@progers
Copy link
Contributor

progers commented Feb 3, 2025

It is not clear how text-size-adjust and em units should interact (spec). Not adjusting em units leads to clipped text. Adjusting em units leads to double-adjustment of line-height.

Example (live):

<!doctype html>
<meta name="viewport" content="width=device-width">
<style>
  html { -webkit-text-size-adjust: 250%; text-size-adjust: 250%; }
</style>

<p>
  height (em, px):
  <div style="height: 1em; width: 16px; background: lightblue; display: inline-block;">a</div>
  <div style="height: 16px; width: 16px; background: lightgreen; display: inline-block;">a</div>
</p>

<p>
  border-width (em, px):
  <div style="border-top: 1em solid blue; width: 16px; background: lightblue; display: inline-block;">a</div>
  <div style="border-top: 16px solid green; width: 16px; background: lightgreen; display: inline-block;">a</div>
</p>

<p>
  line-height (em, px):
  <div style="line-height: 1em; width: 16px; background: orange; display: inline-block;">a</div>
  <div style="line-height: 16px; width: 16px; background: purple; display: inline-block;">a</div>
</p>

Comparison between Webkit (left) and Chromium (right):
Image

@tabatkins
Copy link
Member

tabatkins commented Apr 29, 2025

This seems more generally to be a question about what text-size-adjust actually does.

Based on both discussion and testing, it seems the behavior definitely doesn't match the spec, but also differs significantly between iOS and Android. In particular:

  • In both Android and iOS, text-size-adjust changes the value of both font-size and line-height, independently. (The spec doesn't mention line-height, and this isn't just a consequence of line-height: <number> responding to the em size; fixed px-length line-heights are also scaled, and have to be unless you want overlapping text.)
  • Also in both browsers, the adjustment does not happen at computed-value time (which the spec says it does). Instead, it happens somewhere around used-value time. This is very important, because applying it at computed-value time would cause issues; you'd get the adjustment compounding across descendents.
  • In Android, text-size-adjust only affects font-size and line-height, and as it's a used-value time adjustment, em units do not reflect the adjusted size. You can see this in @progers's image, above, where the em sizes and the px sizes are always identical, and reflect the pre-scaling font-size.
  • In iOS, it affects font-size and line-height, but it also scales the em size (again, as you can see in @progers's image). But, crucially, it does not do this by affecting how ems canonicalize to px at computed-value time; inheritance sees the original, unscaled size. (Doing so would cause descendants to compound the scaling.) However, it does still end up compounding twice when you have a line-height written in ems - line-height is scaled as normal, and then because it contain an em length, it gets scaled again. You can, again, see this in @progers's image, the lower left corner, where the orange line-height is doubled to match the scaled text and then doubled again to be extra-big.

Philip didn't test explicitly for the effects of inheritance; you can see it in this test case (load it up on a phone, or in an emulator; text-size-adjust has no effect on desktop atm):

<!DOCTYPE html>
<p class=unscaled>unscaled text</p>
<div id=parent>
  parent
  <div id=child>
    child
  </div>
</div>

<style>
html { font-size: 16px; }
.unscaled, #parent, #child { font-size: 2em; margin: 0; }
#parent { text-size-adjust: 300%; -webkit-text-size-adjust: 300%; }
#parent, #child {
 height: 1em; 
 background: repeating-linear-gradient(white 0 16px, silver 0 32px);
 outline: dotted thin red;
 padding-left: 10px;
}
</style>

The gradient bars are there for scale - each bar is 16px tall (1rem) so you can easily tell how large things are.

On Android, the parent is 2rem tall and the child is 4rem tall, as shown by the background and outline, exactly what you'd get on desktop. text-size-adjust then takes effect, making the text and line-heights of each 3x larger so they overflow.

On iOS, the parent is 6rem tall and the child is 12rem tall, meaning their height: 1em declarations were scaled by text-size-adjust. However, their font-size declarations were not scaled before inheritance; if they were, the child would see a 1em == 6rem equivalance, set its own font-size to 2em (aka 12rem) and then the height: 1em (12rem) would get scaled again to produce a total height of 36rem. This doesn't happen, tho, which indicates that iOS must be doing some variety of "magic" late (post-computed) adjustment of em sizes (possibly it is done at computed-value time, but font-size inheritance is magically using the pre-scaled value? someone needs to look at the impl to tell).


I'm not entirely sure what our conclusions should be here. However, the spec definitely needs to be fixed in some way, and how we resolve this also affects how we want to specify the (pem unit](#10674), which is meant to be responsive to the scale.

Can someone from Apple shed some light on how exactly iOS text scaling works? I'm not sure who works on text over there anymore - @hober @stubbornella help?

The important bit here is that the behavior is almost certainly at least somewhat motivated by compat, but the lack of compat in em behavior makes ems actually unusable on the mobile web if you don't turn off text-size-adjust manually. (We have an internal page at Google giving guidance on how to style the search results page, and one part of the advice is just "don't use ems, they're not interoperable", because the search results page definitely does allow for text scaling.)

I suspect that we still want to minimize the amount of magic we introduce for this, so there's two possible routes I find that are relatively minimal in magic:

  1. The Android behavior, where font-size and line-height are adjusted at used-value time (and thus ems do not adjust). We would then let pem be the adjusted version, exactly equivalent to just writing calc(1em * env(preferred-text-scale)).
  2. Something close to the iOS behavior, but not identical: font-size and line-height are adjusted at computed-value time, but we add some magic to both font-size inheritance (and line-height's computation of ems) that removes the scale, so you don't get double application. pem could be defined a few ways, then:
    1. we could have it just be a shortcut for the calc(...) (which would double-apply the scale when text-size-adjust was active),
    2. or we could have it be a shortcut for the calc() when text-size-adjust isn't active but equivalent to em when it is (so a pem size would be identical under both text-size-adjust: none and text-size-adjust: auto),
    3. or we could have it just be a fixed size based on scaling the default font size, kinda like a rem unit, and let authors use text-size-adjust + em when they want to respect the scaling "locally".

I lean slightly towards the Android behavior here, because of its simplicity in the model, but I could be convinced either way.

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

No branches or pull requests

3 participants