v153 · css · scrolling · shipped
scroll-axis-lock
The scroll-axis-lock CSS property lets an author instruct the browser not to constrain a user's scrolling gesture to a single axis. Set scroll-axis-lock: none on a scroll container and diagonal panning follows the exact vector of the user's touch, trackpad, or wheel input from the very start of the gesture — the behavior 2D interfaces such as maps, diagrams, and infinite canvases need from native scrollers.
The milestone=153 listing files this feature as “Enabled by default” (the listing is authoritative for gendn), and the Intent to Prototype and Ship states “Will ship enabled for all users”. Two stability caveats remain on the record as of 2026-07-29:
- The normative text is w3c/csswg-drafts PR #14152 (
[css-overflow-5] Introduce scroll-axis-lock), which is open and unmerged — neither the CSS Overflow Module Level 5 nor the CSS Scroll Snap Level 2 Editor's Draft renders the property yet. The grammar below is quoted verbatim from the PR. - The Chromium runtime feature
ScrollAxisLockis still statusexperimentalin runtime_enabled_features.json5 at trunk (fetched 2026-07-29). On a build where the rollout has not reached you, launch Chrome with--enable-blink-features=ScrollAxisLockto test locally; the intent records no about://flags entry (“No information provided”).
at a glance
| What it is | A CSS property, scroll-axis-lock, that controls whether the browser may “lock” (rail) a user's scrolling gesture to one axis on an element — auto keeps the default heuristic, none opts the element out |
|---|---|
| Grammar | auto | none — initial auto, not inherited, animation type discrete |
| Applies to | All elements per the PR's propdef; the CSSWG resolution phrases it as scroll containers — the lock only has an observable effect on elements that can actually scroll (see context) |
| Milestone listing | Chrome 153 — Enabled by default (listing, verified 2026-07-29) |
| Chromium implementation | Property registered in css_properties.json5 (keywords auto/none, default auto) behind runtime flag ScrollAxisLock (tracking bug crbug.com/479472367) |
| Spec status | csswg-drafts PR #14152 open, targeting CSS Overflow Module Level 5; TAG review pending |
| WPT | css/css-overflow/scroll-axis-lock.html — 2/2 subtests passing in the latest Chrome (experimental) run on wpt.fyi (fetched 2026-07-29) |
| ChromeStatus | 5908461532610560 — scroll-axis-lock (Blink component Blink>Scroll) |
why it exists
When a scroll gesture starts with significantly more movement in one axis than the other, browsers “lock” the gesture to the dominant axis and zero out the perpendicular deltas — a heuristic that stops accidental sideways drift while reading vertically-scrolling content. For interfaces that must pan freely in two dimensions, that same heuristic is a bug: the user has to start every gesture at a careful angle, or restart it once locking kicks in.
Until now the only escape was to bypass native scrolling entirely: set touch-action: none, track touch or wheel deltas in JavaScript, and apply them via transforms or scripted scrollTop/scrollLeft updates. That costs main-thread input latency and jank, re-implements gesture state by hand, and interacts badly with scroll-dependent platform features such as scroll events, scroll snapping, IntersectionObserver, and scroll-driven animations. scroll-axis-lock: none keeps native scrolling — on the compositor — and just disables the locking heuristic.
how axis locking works (the behavior being controlled)
The specification text (PR #14152) describes the heuristic directly: when handling a scroll resulting from a user's gesture, user agents often lock the resulting scroll to one axis — zeroing out the deltas in the perpendicular axis. The PR's own example: a drag that would scroll an element 500px vertically and 3px horizontally is interpreted as an intended vertical scroll; the UA ignores the 3px delta and all further x-axis deltas for the remainder of the gesture. The lock is therefore gesture-scoped: it is decided at the start of a gesture and persists until that gesture ends.
Axis locking applies across input modalities — touchscreen drags, trackpad pans, and mouse-wheel scrolls — which is exactly why the explainer rejected overloading touch-action (that property filters whether the browser intercepts a touchscreen gesture at all, and has no effect on wheel or trackpad input). The WPT test exercises both touch and wheel paths: in each, a steep 2px-horizontal / 150px-vertical scroll on a scroll-axis-lock: none container must not rail — scrollLeft must land within tolerance of the sent delta rather than being zeroed.
syntax
Formal definition, verbatim from csswg-drafts PR #14152 (targeting CSS Overflow Module Level 5):
Name: scroll-axis-lock
Value: auto | none
Initial: auto
Applies to: all elements
Inherited: no
Computed value: specified value
Animation Type: discrete
Canonical Order: per grammar
A declaration is a single keyword:
scroll-axis-lock: auto; /* default: UA may rail gestures to one axis */
scroll-axis-lock: none; /* UA must not rail gestures on this element */
Chromium's registration matches: keywords: ["auto", "none"], default_value: "auto", field_template: "keyword", typedom_types: ["Keyword"], gated by runtime_flag: "ScrollAxisLock" in css_properties.json5. The CSSWG resolution that adopted the property reads: “RESOLVED: scroll-axis-lock:auto|none, applies to scroll containers, not inherited, UNLESS smfr objects” (csswg-drafts #13207, 2026-01-28) — no objection from smfr is on record in the issue, and the PR propdef above is the resulting text.
values
| Value | Contract and effect |
|---|---|
auto | “The user agent may lock scrolls on the element to one axis.” (PR #14152, verbatim) — default behavior: the UA's axis-locking heuristic applies to gestures on the element. Existing pages behave as they always have. |
none | “The user agent must not lock scrolls on the element to one axis.” (PR #14152, verbatim) — the gesture's diagonal vector is honored from the first input event: both axes scroll simultaneously, no railing, for the whole gesture. |
The property takes exactly one keyword; there are no per-axis variants (the explainer's scope is an all-or-nothing opt-out, not a per-axis lock control). none on a container that only overflows in one direction has no practical effect — there is no perpendicular content to drift into, so the browser cannot scroll that way regardless.
context: where the property applies
- Applies to “all elements” per the propdef, “scroll containers” per the resolution. The adopted resolution says the property applies to scroll containers; the PR's propdef box says “all elements”. gendn records both rather than resolving the wording difference: the property can be specified on any element, but it only produces observable behavior where axis locking can occur — on a scroll container being scrolled by a user gesture.
- Not inherited. Every scrollable component opts in (or not) individually — a deliberate decision so that, for example, a vertically-locked article column can sit inside an unlocked 2D map application (explainer — inheritance considered and rejected). There is also no viewport propagation from
html/bodythe wayoverflowpropagates. - All input modalities. Because it is defined as a CSS Overflow behavior rather than an input filter, it governs touchscreen, trackpad, and mouse-wheel gestures alike (the WPT test drives both touch and wheel).
- Not exposed to script beyond CSSOM. It is an ordinary CSS property: readable via
getComputedStyle()and settable viaelement.style.scrollAxisLock/ CSS Typed OM (typedom_types: ["Keyword"]in the Chromium registration). There are no events, promises, or JavaScript entry points. - Naming history. The proposal began as
overflow-axis-lock; the CSSWG chose thescroll-prefix because the property describes scrolling behavior and gesture handling rather than the layout/clipping behavior ofoverflow— consistent withscroll-snap-typeandscroll-behavior(explainer — alternative names). Searching older threads for the current name may miss this history.
behavior: what each value produces
| Scenario | auto (default) | none |
|---|---|---|
| Gesture starts steeply vertical (500px y, 3px x) on a 2D-scrollable container | UA may zero the 3px x-delta and all further x-deltas for the rest of the gesture — the scroll is railed to y | Both deltas applied from the first event; the scroll follows the true diagonal vector |
| User breaks out of the lock mid-gesture (moves far enough along the other axis) | UA-defined; the spec text does not define the breakout threshold — the heuristic is intentionally UA territory | No lock exists, so there is nothing to break out of |
| Wheel scroll with a small x component (trackpad two-finger pan) | May rail to the dominant axis (wheel path is railed the same way — the WPT wheel subtest exists to prove none disables it) | Wheel deltas applied as sent — the test asserts the observed wheel event's deltaX matches the sent delta |
Programmatic scrolling (scrollTo(), scrollTop = …, scroll-snap) | Unaffected either way — the property governs locking of user gesture scrolls; nothing in the sources restricts script-driven scrolls | |
The heuristic itself (angle thresholds, breakout distance) remains UA-defined — the property is an opt-out switch, not a parameterization of the heuristic. The spec text defines only what none forbids and what auto permits.
invalid input and edge behavior
| Situation | Behavior |
|---|---|
Unrecognized value (e.g. scroll-axis-lock: both) or wrong token count | The declaration is invalid and dropped at parse time — standard CSS error handling (CSS Syntax § invalid at computed-value time); the computed value falls back to whatever the cascade otherwise produces (auto by default). No exception, no console error contract. |
Global keywords (inherit, initial, unset, …) | Accepted as for every CSS property; inherit copies the parent's computed value even though the property is not inherited by default. |
none on a non-scrollable or single-axis-overflowing element | No observable effect — there is no perpendicular scroll to unlock (see values). |
| Unsupported browser (property unknown) | The declaration is ignored entirely; behavior stays at the UA default (auto-equivalent). Guard with @supports if the unlocked experience is load-bearing (see examples). |
| Changing the property mid-gesture | Not specified — neither the PR text nor the explainer defines when during a gesture a change takes effect (recorded as an open question in the critique). |
lifecycle: when the decision is made
- Style resolution — the property computes to the specified keyword per element (discrete animation type: it flips, it does not interpolate).
- Gesture start — on the first scroll-causing input of a user gesture, the UA's scrolling engine decides whether to lock, per the element's computed value:
autolets the heuristic run;noneskips it. - During the gesture — under
auto, a taken lock zeroes perpendicular deltas for the remainder of the gesture (the PR's example is explicit that the lock persists for the whole gesture). Undernone, every delta is applied as input. - Gesture end — any lock lapses with the gesture; the next gesture re-evaluates from scratch. No state persists between gestures, and there is nothing to clean up — the property is idempotent style input, not a subscription.
examples
A 2D map viewport that pans diagonally from the first touch (the explainer's canonical example):
.map-viewport {
width: 100vw;
height: 100vh;
overflow: auto;
/* Diagonal panning works immediately on touch, trackpad, and wheel */
scroll-axis-lock: none;
}
.map-content {
width: 5000px;
height: 5000px;
}
A swipe-to-dismiss card that follows the finger in 2D instead of snapping to a vertical track:
.dismissable-card {
overflow: auto;
scroll-axis-lock: none;
/* translate the card with the gesture; dismiss past a threshold */
}
Progressive enhancement — apply the unlock only where it is understood, and keep a deliberate choice elsewhere:
@supports (scroll-axis-lock: none) {
.pan-canvas { scroll-axis-lock: none; }
}
The Chrome Platform Showcase entry for this feature has no live demo route yet (route HEAD-checked 404, 2026-07-29); a meaningful demo also needs real gesture input, which a headless embed cannot drive. When a demo lands it will appear at that URL.
Source: explainer — proposed solution (map example, verbatim pattern); explainer — use cases (swipe-to-dismiss); chrome-platform-showcase (404 at fetch time)accessibility considerations
Axis locking exists for users: people who find it difficult to swipe in a straight line rely on it to keep unidirectional feeds and articles from drifting sideways. The explainer's mitigations are built into the design:
- Opt-in by default. The initial value is
auto, so the stabilizing lock stays on unless an author deliberately disables it per container. - Limited blast radius.
noneon a unidirectional container cannot cause drift — there is no overflowing content in the perpendicular direction to scroll into. - Benefit where intended. On genuine 2D surfaces (maps, large canvases, zoomed images), removing the lock eliminates the extra gesture friction of fighting the railing — the smoother experience those users were already owed.
Authors should still treat none as a deliberate UX choice: on content that is primarily read along one axis, disabling the lock makes accidental diagonal drift more likely for exactly the users the heuristic protects.
web platform tests
The feature has upstream coverage: css/css-overflow/scroll-axis-lock.html, with two promise_test subtests — one driving a touch drag, one a wheel scroll — each sending a steep 2px-horizontal / 150px-vertical scroll at a scroll-axis-lock: none container and asserting the horizontal component is applied (within tolerance) instead of being railed to zero; the wheel subtest additionally asserts the dispatched wheel event's deltaX matches the sent delta. In the latest Chrome (experimental) run on wpt.fyi — Chrome 152.0.7977.7, 2026-07-29 — the test passes 2/2 (results). The intent thread answers “Is this feature fully tested by web-platform-tests?” with “Yes” pointing at the same test.
One caveat: the test's <link rel="help"> points at css-scroll-snap-2/#scroll-axis-lock, an anchor that does not exist — the normative text lives in the unmerged css-overflow-5 PR, and neither Editor's Draft renders the property yet (both fetched 2026-07-29). Treat the test's help link as stale, not as evidence of a second spec home.
browser compatibility
Interim table. There is no BCD entry (BCD css/properties/ has no scroll-axis-lock.json, verified 2026-07-29) and no web-features entry (webstatus.dev query, zero matches). Rows are compiled from the linked primary sources, not from BCD.
| Browser | Support | Evidence |
|---|---|---|
| Chrome | 153 — Enabled by default per the listing; runtime flag ScrollAxisLock still experimental at trunk (2026-07-29), rollout per the intent is “enabled for all users” | milestone listing; flag record; intent rollout plan |
| Edge | Not separately reported | Chromium-based; no separate position on the API record |
| Firefox | No signal | Mozilla #1435 (open at fetch time; position not recorded) |
| Safari | No signal | WebKit #698 (position not recorded at fetch time) |
Web developer signal on the intent is “Positive”, citing supporting feedback on csswg-drafts #13207. The intent records support on all six Blink platforms (Windows, Mac, Linux, ChromeOS, Android, Android WebView).
Source: chromestatus.com/feature/5908461532610560; Intent to Prototype and Shipsecurity and privacy
- No new information exposure. The property exposes no hardware sensors, user identity, or cross-origin information; it alters only the threshold heuristic applied to pointer input the page is already authorized to receive (explainer, verbatim position).
- Same-origin boundaries unchanged. No scroll state or content leaks across origins; the feature introduces no storage, networking, or scripting surface of its own.
- Styleable-only. As a CSS property it is subject to the usual CSS caveats (it is observable via
getComputedStyleon same-origin elements) and nothing more.
specifications
| Document | Status |
|---|---|
| w3c/csswg-drafts PR #14152 — [css-overflow-5] Introduce scroll-axis-lock | Open, unmerged (fetched 2026-07-29) — the normative propdef and value definitions quoted on this page; targets CSS Overflow Module Level 5, whose Editor's Draft does not yet render the section |
| w3c/csswg-drafts #13207 — [css-overflow-5] Allow controlling scroll axis locking behavior | Adopted by CSSWG resolution, 2026-01-28 (“scroll-axis-lock:auto|none, applies to scroll containers, not inherited, UNLESS smfr objects”) |
| scroll-axis-lock explainer (proponents: Blink Interactions) | The design document: use cases, rejected alternatives, accessibility & security considerations |
| w3ctag/design-reviews #1248 — CSS scroll-axis-lock | Open — TAG review status “Pending” on the intent (fetched 2026-07-29) |
see also
- Chrome Platform Status — scroll-axis-lock (API record)
- blink-dev — Intent to Prototype and Ship: scroll-axis-lock
- MDN — touch-action (the gesture-filter property the explainer deliberately did not overload; MDN covers it, not this feature — the
scroll-axis-lockproperty page 404s and search returns no match, 2026-07-29) - MDN — overscroll-behavior (the neighboring scroll-chaining control — a different axis-level behavior)
- gendn — Single-axis scroll containers (the other axis-level scrolling feature in Chrome 153:
overflow: scroll clipper-axis control. Complementary, not overlapping — that feature shapes which axes can scroll; this one shapes how gesture locking treats axes that do) - Chrome Platform Showcase — scroll-axis-lock (no demo route yet at fetch time)