Re: [csswg-drafts] [css-size-adjust] Clarify interaction between text-size-adjust and em units (#11636)

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; see later points on the details.) 
* 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 `em`s 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 `em`s - `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](https://software.hixie.ch/utilities/js/live-dom-viewer/?%3C!DOCTYPE%20html%3E%0A%3Cp%20class%3Dunscaled%3Eunscaled%20text%3C%2Fp%3E%0A%3Cdiv%20id%3Dparent%3E%0A%20%20parent%0A%20%20%3Cdiv%20id%3Dchild%3E%0A%20%20%20%20child%0A%20%20%3C%2Fdiv%3E%0A%3C%2Fdiv%3E%0A%0A%3Cstyle%3E%0Ahtml%20%7B%20font-size%3A%2016px%3B%20%7D%0A.unscaled%2C%20%23parent%2C%20%23child%20%7B%20font-size%3A%202em%3B%20margin%3A%200%3B%20%7D%0A%23parent%20%7B%20text-size-adjust%3A%20300%25%3B%20-webkit-text-size-adjust%3A%20300%25%3B%20%7D%0A%23parent%2C%20%23child%20%7B%0A%20height%3A%201em%3B%20%0A%20background%3A%20repeating-linear-gradient(white%200%2016px%2C%20silver%200%2032px)%3B%0A%20outline%3A%20dotted%20thin%20red%3B%0A%20padding-left%3A%2010px%3B%0A%7D%0A%3C%2Fstyle%3E) (load it up on a phone, or in an emulator; `text-size-adjust` has no effect on desktop atm):

```html
<!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](https://github.com/w3c/csswg-drafts/issues/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 `em`s 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 `em`s, 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 `em`s 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 `em`s) 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.

-- 
GitHub Notification of comment by tabatkins
Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/11636#issuecomment-2840207330 using your GitHub account


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Tuesday, 29 April 2025 20:46:47 UTC