A phone with a notch cut the top off our app. The fix was to add
padding: env(safe-area-inset-*) to the app shell, which is what every answer online says. It
shipped, the changelog said the shell was fixed, and the screen looked exactly the same,
because that rule had never applied to a single pixel.
An absolutely positioned element resolves top, right, bottom and
left against its containing block's padding box. The edges of the padding box
lie on the outside of the padding, not the inside.
So top: 0 puts the child at the parent's border edge, which is where the padding starts. The
child then covers the very padding that was supposed to hold it back:
.app { padding: env(safe-area-inset-top) 0 env(safe-area-inset-bottom); }
.pane { position: absolute; inset: 0; } /* sits on the border edge, ignores all of it */
If every child of that container is absolutely positioned, the padding affects nothing at all. Ours had
four absolutely positioned panes and everything else position: fixed, which resolves against
the viewport and is even further out of reach. A complete no-op, twice shipped.
Move the value to the element that is actually positioned:
.pane {
position: absolute;
top: env(safe-area-inset-top);
bottom: env(safe-area-inset-bottom);
left: 0; right: 0;
width: auto; height: auto; /* important, see below */
}
Reset any width: 100% or height: 100% at the same time. A
positioned box with both a top offset and a 100% height is 100% of the container plus the offset,
so it overflows by exactly the inset you just added and you are back where you started, with a scrollbar.
Two seconds each, and either would have caught this the first time:
top
against the parent's content-box origin. If they are the same number, the padding is not in the
chain.The general habit is worth more than the CSS: confirm a rule takes effect before reasoning about why its effect is wrong. Most of the time debugging a wrong result is the harder problem, and this was not that.
inset: 0 inside a padded container. Same
mechanics, no notch required.position: fixed children, which resolve against the viewport and ignore
every ancestor's padding entirely. Unless an ancestor has a transform, filter
or will-change, which makes it the containing block and changes the answer again.scroll-padding is the property you actually wanted.The changelog claimed the app shell was fixed. It was not, and it was wrong in a way nobody could catch without the device, because the code change was real and the applied result was nothing.
When a fix depends on hardware you do not have, ship it and say it is unconfirmed. That is honest, and it keeps the next person from treating the entry as evidence and looking somewhere else.