Safari :has(:empty) bug
Safari, Mac and iOS, only.
I probably got some of the details wrong, but I'm fairly certain of the gist.
I commented on the relevant Bugzilla issue.
Update: Fixed.
There should not be any rule such as x:empty + y,
without a :has, that holds CSS declarations and selects the same element y
as in the code examples below. If there is, all styles work as intended.
If there isn't...
- Take an element
xthat may or may not be empty, and an elementythat is styled to react tox's emptiness. - The selector must contain a
:has(:empty).
Example selectors arey:has(x:empty)
orz:has(x:empty) y. - Now, on page load,
x's emptiness is evaluated correctly — but only once.
However,
- IF the
bodyvertically overflows thehtmlelement and not horizontally. - AND element
xis filled by a script that runsonload
(onDOMContentLoadeddoes not trigger the bug, and neither does executing the script by a direct function call later in the HTML page.) - THEN on page load
xis treated as being empty as far as styles onyare concerned.
So far x's emptiness is only evaluated once. Whatever happens to x later, y doesn't change styles
- IF the overflow/onload bug is present
- AND
yhas any CSS length in vw, vh, cqw, or other viewport-dependent units - THEN resizing the window when
xis filled removes the styles fromy— permanently. (On iOS, an orientationchange counts as a resize.)
If y has a display: none
- its parent must have a length in a viewport-dependent unit. In that case, resizing along the correct axis when
xis filled removes the styles fromy.
(i.e. resizing horizontally when vw, cqw etc are present, or resizing vertically if vh, cvqh, etc are present) - the requirement that
bodymay not overflow thehtmlelement horizontally is dropped.
As far as I can see :has(x:empty) is checked for emptiness only twice:
- between DOMContentLoaded and load,
- and, only if the overflow/onload bug is present, after the first resize along the correct axis, provided a viewport-dependent length unit is present.
Most tests done on Mac 26, but iOS 18 also has this bug, and I suspect 26 has, as well.
Solving the bug
If you just want to solve the bug in your production code, try writing a selector without :has(:empty).
If you can't, add a non-:has selector such as the one below that just defines a variable you never use. It's OK to select way more elements than your :has(:empty) one does, as long as your element y is also selected.
:empty ~ * {
--test: 'nonsense';
}
If you're OK with emptiness being evaluated only once, fire your script onDOMContentLoaded, and not onload.