Chrome 153 ยท stable 8 Sep 2026 ยท feature-detected

Two axes

A horizontal strip inside a vertical page, with one label that has to stay pinned to the left of the strip and to the top of the viewport at the same time. That has never been possible in CSS, because an element with hidden overflow on one axis becomes a scroll container on both. One new value fixes it. Scroll this page and watch the two labels disagree.

scroll the page, and scroll each strip sideways
CSS overflow: scroll clip 0 scroll listeners ยท 0 layout reads
Sticky, both
Pinned left by the strip, pinned top by the page.
01
Cafe, Poblacion
02
Clinic, BGC
03
Bar, Cebu
04
Studio, Makati
05
Roastery, Ortigas
06
Gym, Alabang
checking what this browser can doโ€ฆ
JavaScript overflow-x: auto; overflow-y: hidden + a listener 0 events ยท 0 reads
Sticky, faked
Left is CSS. Top is a scroll listener, a rect read and a transform, every frame.
01
Cafe, Poblacion
02
Clinic, BGC
03
Bar, Cebu
04
Studio, Makati
05
Roastery, Ortigas
06
Gym, Alabang
running the workaround: this is what the CSS above deletes
Scroll events handled0

Every one of them on the main thread, by definition: a scroll listener cannot run anywhere else.

Layout reads0

getBoundingClientRect calls. Each one can force a synchronous layout.

Main-thread time0.0 ms

Cumulative time inside the handler. Small per call, and it is per call.

The CSS panel cost0.0 ms

There is no handler to time. The number is zero and stays zero.

Read this before the panels. Chrome 153 reaches stable on 8 September 2026. Until your browser has it, the left panel cannot work, and it will show you exactly what it degrades to: the label stops sticking to the page and just rides along with the strip. That degraded state is not a broken demo. It is the bug the right panel's JavaScript exists to work around, which is much easier to understand while you can see both.
01 ยท why it never worked

Any hidden overflow makes an element a scroll container on both axes

A horizontal gallery needs its content clipped vertically, so you write overflow-y: hidden. That is the only sensible declaration available, and it has a consequence nobody wants: hidden is a scrolling overflow value. The box becomes a scroll container in the block axis too, one whose scrollable height happens to be zero.

Now put position: sticky; top: 78px on a child. Sticky resolves against the nearest scroll container in that axis, which is now the strip, which never scrolls vertically. So the property is honoured perfectly and does nothing. There is no warning, no invalid declaration, no devtools badge. It simply has no effect, which is why this costs an hour the first time.

The workaround is always the same shape and the right panel is running it: listen to the page scroll, read the strip's position with getBoundingClientRect, work out how far the label should be pushed down, write a transform. It works. It also puts a layout read and a style write on the main thread on every scroll event, on the one interaction where people notice jank first, and it has to be re-derived every time someone changes the layout.

/* Before. The y axis is clipped AND a scroll container. */
.strip { overflow-x: auto; overflow-y: hidden; }
/* โ†’ sticky top resolves against .strip, which never scrolls. Dead. */

/* Chrome 153. The y axis is clipped and NOT a scroll container. */
.strip { overflow: scroll clip; }
/* โ†’ sticky left resolves against .strip, sticky top against the page. */
02 ยท what your browser has

Feature-detected, with the expression printed

From Chrome 153 a new stable ships every two weeks, which is the reason drop 051 exists and the reason nothing on this page says "needs Chrome 153+". It asks instead. Both probes below are one CSS.supports call and the exact string is shown next to the answer, so you can paste it into a console and get the same result.

ยท CSS.supports('overflow', 'scroll clip') โ€” do NOT use this one, see below probing
ยท getComputedStyle(el).overflowY === 'clip' โ€” after setting overflow: scroll clip probing
ยท el.scrollTop = 100 leaves it at 0 โ€” the behavioural test, and the one this page trusts probing
ยท CSS.supports('scroll-axis-lock', 'none') โ€” allow diagonal gestures probing

๐Ÿšฉ The first row is a trap, and it is the detect everybody will reach for. Two-value overflow and the clip keyword have both parsed for years, so CSS.supports answers yes on browsers going back to Chrome 90, none of which have the behaviour. What changed in 153 is not the syntax, it is a computed-value rule: CSS Overflow 3 said that when one axis is clip and the other is a scrolling value, the clip computes to hidden. That coercion is what made the box a scroll container on both axes, and that coercion is what was removed.

So the detect has to look at the computed value or at the behaviour, not at the parser. Reading overflowY back tells you whether the coercion happened. Setting scrollTop and watching it refuse to move tells you the same thing from the other side, and it is the one this page uses to decide what to tell you, because it tests the thing that actually matters rather than a value that could in principle be reported differently.

03 ยท the second one

scroll-axis-lock, for surfaces that genuinely move diagonally

The other scroll item in the same release. Browsers constrain a scroll gesture to one axis, because on ordinary documents a slightly crooked swipe should go straight down rather than drifting sideways. On a map, a board, a timeline or a node graph that assumption is wrong, and until now the fix was to intercept the gesture and reimplement panning.

The surface below sets scroll-axis-lock: none. If your browser has it, a diagonal trackpad gesture moves diagonally. An honest caveat: this is a gesture behaviour, so a mouse wheel cannot demonstrate it, and neither can a script. There is no measurement on this page for this one, because there is nothing I can measure that would not be theatre. Try it with a trackpad or a touchscreen, or take it as a note for later.

drag diagonally ยท 1400 ร— 700 surface
04 ยท what it is worth

One declaration against a listener, a read and a write

01

It is not about the milliseconds

The handler is cheap per call and the counters will show that honestly. The win is that the behaviour moves into the layout engine, where it is correct during momentum scroll, correct on the first frame, and correct when someone changes the header height without telling you.

02

The listener is always slightly wrong

It runs after the scroll, so it is a frame behind under fast input. Sticky is not: it is resolved during layout. This is the same one-frame lag that turns up between a smooth-scroll library and a WebGL canvas, in a smaller and more common place.

03

@supports cannot gate this

@supports (overflow: scroll clip) matches on browsers going back to Chrome 90, because the syntax has always parsed. Gating on it ships the CSS to machines that will coerce it and silently drops the listener they still need. Probe the behaviour once and set a class.

/* WRONG. This matches on Chrome 90, which does not have the behaviour. */
@supports not (overflow: scroll clip) { โ€ฆ }

/* Right: probe the computed value once, at boot, and set a class. */
const el = document.createElement('div');
el.style.cssText = 'position:absolute;left:-9999px;width:60px;height:50px;overflow:scroll clip';
el.appendChild(Object.assign(document.createElement('div'),
  { style: 'width:400px;height:400px' }));
document.body.appendChild(el);
el.scrollTop = 100;
const singleAxis = el.scrollTop === 0;   // 0 = clipped, not a scroller
el.remove();
document.documentElement.classList.toggle('single-axis', singleAxis);

/* Then gate both sides on the class, and the listener retires itself. */
.single-axis      .strip { overflow: scroll clip; }
html:not(.single-axis) .strip { overflow-x: auto; overflow-y: hidden; }
05 ยท receipt

Verify it yourself

> measured on this machine, this visit
[SUPPORT] probing
[TRAP] CSS.supports('overflow','scroll clip') is a false positive on every Chromium since 90. The syntax parsed long before the behaviour existed.
[LEFT] the CSS panel contains no script ยท 0 listeners ยท 0 layout reads ยท 0 ms
[RIGHT] the workaround has not run yet, scroll the page or press the button
[RUN] no scripted scroll yet
[LOCK] scroll-axis-lock is a gesture behaviour and is deliberately not measured here
[NETWORK] 0 outbound requests since load, measured by PerformanceObserver
[ASSETS] 0 CDNs ยท 0 webfonts ยท 0 analytics ยท single HTML file ยท works offline
[LIMIT] This page is not a zero-JavaScript drop. The left panel's behaviour is CSS; the detection and the counters are script.
[LIMIT] The handler timing is the cost of THIS handler. A production one usually does more, so read it as a floor.
[LIMIT] performance.now() is coarsened for security, so a very cheap handler can accumulate less time than it really spends.
Back to the Labs โ†’