Purpose
Browser behaviours that are correct per spec, invisible in the code that triggers them, and have each caused more than one real bug in a TimelyCare app. Read this before building any component that scrolls, clips, or pins.
Each entry names the rule, what it broke, and what to do instead. Add to it when a browser behaviour costs someone a debugging session twice.
1. One scroll axis turns on the other
The rule. If overflow-x and overflow-y disagree — one computes to
visible and the other does not — the visible one computes to auto instead.
Per CSS Overflow 3,
the two axes cannot be independently visible and something else.
So this:
overflow-x: auto; /* what you wrote */
is really this:
overflow-x: auto;
overflow-y: auto; /* what the browser computed */
The box became a scroll container on both axes, and nothing in the source
says so. overflow-x: hidden does the same thing.
What it broke. The admin-web migration report attributes three separate bugs
to this one rule. The one Helix has fixed is the sticky table header: sticky pins
to the nearest scrollport, the wrapper had silently become that scrollport, and
its height was unconstrained — so it never scrolled, the header never had
anything to pin against, and a hand-rolled sticky top-0 on TableHeader did
nothing at all.
The mechanisms that make the rule dangerous, and that any new scrolling component inherits:
| Mechanism | Consequence |
|---|---|
A scroll container is the reference for position: sticky | Sticky children pin to this box, not the page — and not at all if the box is unbounded. |
| A scroll container clips its overflow instead of painting outside it | Content that used to hang out of the box — a popover, a focus ring — gets cut off. |
| A scroll container is the reference for programmatic scrolling | scrollIntoView and anchor navigation resolve against this box rather than the page. |
What to do.
- Treat any
overflow-x-autooroverflow-x-hiddenas declaring a scroll container, and say so in a comment where you write it. - If you need sticky positioning inside it, the container must also be
height-bounded — an unbounded scroll container never scrolls, so sticky
has no scrollport to pin against and silently does nothing. This is why
Table'sstickyHeaderrequiresmaxHeightand warns in development without it (see Table). - If you only wanted horizontal scrolling, you cannot have it alone. Decide what
the vertical axis should do and set it explicitly, rather than inheriting
autoby accident. - Do not wrap a component that already scrolls in a second scrolling wrapper.
Tablerenders its ownoverflow-x-autocontainer; adding another nests two scrollports.
Where this already applies in Helix. Command, Select, DropdownMenu and
ContextMenu content each pair overflow-x-hidden with an explicit
overflow-y-auto, so the computed value is the one the author chose.
Table's container is the exception worth knowing: it carries overflow-x-auto
alone, and therefore is a vertical scroll container by computation. That is
deliberate — it is what stickyHeader pins against — but it is also why
stickyHeader is a no-op without maxHeight, and why wrapping a Table in your
own scrolling div nests two scrollports.