A partial archive of discourse.wicg.io as of Saturday February 24, 2024.

[Proposal] API to Control User Gesture Navigation



Most browsers implement a set of navigations actions that are triggered by user gesture (find a list below). The user-agent relies on heuristics^ to detect if the scroll gesture should navigate or whether it should be handled by the page. These heuristics may work in general case but fail for certain applications. Such failures are especially problematic because a navigation action is a major context switch that disrupts user experience.

Over time, web developers have come up with workarounds to disable these navigation gestures either to prevent said disruptions or to implement their own customized versions. Unfortunately these workarounds are not always reliable and have major drawbacks. At Chromium, we have heard from web developers about the need for a simpler and more reliable way to do this. This document proposes a standard CSS based API which replaces many of these workarounds.

A survey of current gesture navigations:

  • Pull-to-refresh: Mobile Chrome and Opera
  • Over-scroll swipes: Chrome, Desktop Safari, Desktop Firefox, IE, Edge^^
  • Scroll from edge: Mobile Safari

^: For example, requiring the scroll gesture to be of certain magnitude, be in certain direction, or start when scroller is at its limit.

^^: Current stable release of Microsoft Edge has no gesture navigation but they are adding support for overscroll swipes due to popular demand.

Pull-to-Refresh (p2r) Problem

Pull to refresh is a feature of Mobile Chrome (and Opera) where once the user over-scrolls the page we show a refresh animation and refresh the page. The UX pattern was popularized by native mobile applications as a way to fetch new content which in the context of the browser translates to refreshing the page. However refreshing the page does not make sense and is not relevant for certain mobile web applications and can be disruptive and costly.

Below is a list of few (see this stackoverflow thread for more) workarounds used by apps to disable pull-to-refresh behaviour which come with their own negative consequences:

  1. Disable document scrolling, move all content in a scrollable div
    • Cons: forces a certain layout and loses top controls behaviour.
  2. Disable vertical scrolling all together using ‘touch-action: none;’
    • Cons: Opting out of the user-agent scrolling machinery is over-kill and adds a lot of complexity to the application.
  3. Add touch event handlers and prevent scroll if gesture is scrolling vertically in a way that induces a refresh (example: AMP implementation)
    • Cons: touch event handlers has perf implications for threaded scrolling and should be avoided if possible. Also this can become flaky if the app hits touch ack timeout.

Swipe Navigation Problem

Most major mobile and desktop browsers provide a swipe navigation feature where they navigate backward and forward in history when a user swipes left or right and over-scrolls the page. In some page layouts (e.g., a page with edge-to-edge horizontal image carousel) it is very easy for a user to trigger these navigation actions accidentally once they have reached the page extent.

There are several workarounds but as similar to p2r ones they are not simple and have draw-backs and none works in Mobile Safari.

  • Prevent-defaulting touch event handlers (or wheel event for desktop browsers) similar to p2r.
  • Using touch-action: pan-y on document to prevent horizontal (or vertical pan)
  • Use scroll chaining: This is an IE specific workaround and disables scroll chaining (--ms-scroll-chaining: none) on root scroller. This actually does not work well in practice as it only prevents overscroll action if the page is not scrolled fully to its extent and requires the page to be scrollable

Examples of developers trying to find a solution:

Out of Scope

Navigation gestures that do not need to compose with page content, e.g., swiping URL bar.

Also cosmetic over-scroll effects such as glow, or bounce are out of scope of this proposal. Unlike navigation, these affordances do not cause a drastic context-switch and usually play a role in keeping platform UI consistent so browsers may not be willing to expose a way to disable them. However this proposed API does not prohibit its extension in order to cover that case if there is interest.

Proposed API: overscroll-action

Introduce a new non-inherited CSS property overscroll-action, that may be applied on the root element and applies to the viewport. It controls the permitted overscroll actions by the browser on each axis. Each property accepts two values that control the permitted overscroll on its individual physical directions i.e., left, right, and top, bottom.

overscroll-action-{x,y}: [auto || none]{1,2}

  • auto: the user agent determines appropriate default action for overscrolls in this axis and direction.
  • none: overscroll in this axis and direction should not trigger default action such as navigation. This does not impact UI effects such as bounce or glow.

Allows controlling x and y axis separately e.g., disable p2r without affecting gesture nav. This means that different components may control the behaviour on each axis without having to coordinate (e.g., a routing library may want to control vertical overscroll, and a carousel may want to control horizontal overscroll).

The idea for using a CSS property is originally mentioned here. It is simple to understand and use.

Note that this property does not prevent us from later adding corresponding javascript event that allows customization of p2r.

Syntax alternatives:

  • Shorthand syntax: overscroll-action: [auto || none] {1,4}. A shorthand syntax can be used to control both axis in a single property. Allowing this syntax has the risk of encouraging developers to naively use overscroll-action: none; as a quick way to disable p2r and inadvertently disable swipe navs.
  • Explicit action syntax: overscroll-action: [auto || [navigate-x | navigate-y] || none]. This syntax forces developers to be explicit about the actions that they want to prevent. This can be useful if we expect multiple different overscroll actions that the application want to individually control. Also perhaps easier to expand if we want more granular control such as per-direction (e.g., navigate-left vs navigate-right).

overscroll-action for inner-scrollables

Non root scrollables may have default overscrolling actions such as glow or bounce effects. The current proposal only intends the overscroll-action property to be applicable on the root scroller and cover navigation actions. Expanding this property to be applicable to non-root scrollers for controlling glow, or bounce effects is outside the scope of this proposal.

Feature Detection and Polyfillability

It is should be rather straight forward to detect existence of CSS property. A polyfill may fallback to one of the existing workarounds which are unfortunately unreliable and have limited applicability.

Alternative Solutions

  • Viewport Meta Tags: A CSS, or Javascript based solution is preferable to meta tags.
  • App Manifest: This was rejected as being out of scope for app manifest.
  • Refresh Event (www-dom proposal by rbyers@): This seem to be favored by some web developers as it provides a path to customize the “refresh” action and not just disabling it.
    • Does not cover swipe navigations but perhaps a more generic navigation event can be used.
    • This ties navigation actions to the main thread (aka slow path) which can introduce delay into navigation process.
    • Mozilla engineers were concerned that such an event should be applicable to all refresh affordances on all platforms with an escape hatch for hard reloads and not just p2r.
    • See additional discussion below on Disabling vs Customizing.
  • Attributes on navigator (e.g., Navigator.refreshEnabled, Navigator.swipeNavigationEnabled): This works fine but it seems a CSS property is more natural.

Customizing vs Disabling P2R

Some web applications (e.g., twitter) may be interested in keeping the p2r UI affordance but hook their own custom refresh logic replacing the browser’s default. This is certainly a valid usecase and should also be addressed. However we think it is worth addressing these two separately for the following reasons:

  1. The cost of not being able to disable seems to be greater than not being able to customize. Also anecdotally it appears that most apps just want to disable rather than customize so we want to make sure there is a simple API for that. So it is important to address disable first.
  2. Once we have a reliable way to disable UA’s behaviour, it becomes much easier to create customization on top.
  3. p2r is a Chrome only feature at the moment. This proposal focuses on a solution that is useful for both p2r and swipe navigations across browsers.
  4. There is some awkwardness in addressing both the Disable and Customize usecases using a single event e.g., deciding whether p2r animation should be shown or not.



This is great!

It’s a common source of debate whether browsers should have gestures that potentially interfere with how the application intends to handle input (eg. many argue Chrome pull-to-refresh feature shouldn’t exist). But in Chrome we at least believe strongly in the principle that the application should have maximal control over the experience (in fact that’s where the name “Chrome” came from). We thought we were doing that with the P2R feature (can be disabled by cancelling touch events) but it turns out that’s too hard and sometimes even impossible in practice. So having some mechanism like this for authors to reliably opt-out of the behavior seems REALLY important to me.


In a very vague sense, this almost feels like it cuts across what IndieUI Events wanted to address - being able to hook into abstract, high-level events (and then being able to preventDefault and handle it yourself) https://www.w3.org/TR/indie-ui-events/ … however, as the scope for IndieUI has, as I understand it, been heavily cut down, and - to my knowledge - there are no actual implementations in the wild for it…it may well be that a more declarative approach may be better.


being able to hook into abstract, high-level events (and then being able to preventDefault and handle it yourself)

I agree that the problem we are trying to solve may also be addressed in some way by a high-level event. We have considered having refresh/navigation event. It also could fit in indie-ui or device independent event (though neither specifically mentions over-scroll).

however, as the scope for IndieUI has, as I understand it, been heavily cut down, and - to my knowledge - there are no actual implementations in the wild for it…it may well be that a more declarative approach may be better.


In any case, a declarative solution here does not prevent having an event-based solution in future and in fact one can argue that we want both eventually. Also an advantage of the declarative approach is that the user-agent would know in advance the the over-scroll behaviour is disabled and it does not need to wait for event handler to preventDefault. This can be a performance benefit for threaded scrolling.


Do you have any info how many people are using iOS Safari’s gesture navigations in browser?


I don’t have any stats on how popular this feature is on iOS Safari. Are you looking for anything in particular from that stats? I may be able to get some data from Chrome usage stats.


I am just thinking if I should optimize for such gestures. Most web apps with animation and pushState look weird on iOS with these gestures. Try Google Plus, for example.


Regarding the iOS Safari behavior, I don’t have stats but anecdotally we hear from teams like Google Photos that users accidentally triggering the navigation behavior is one of the top complaints from users. Eg. see this bug.


I have created a draft spec on GitHub based on this proposal. Feel free to file issues against it. Also after some offline discussions, we think the alternative explicit action syntax is more appropriate for this. More details are in the spec.


Seems like a reasonable scenario to tackle. Not sure on the exact API, but how about we move this into the WICG GitHub to incubate further? I think our folks that own overscroll navigation will have some feedback.


Great. I am happy to move this to WICG to further collaborate on the API. @yoavweiss: is my proposal repo a good candidate to move to WICG? What is the process?

As for the exact API, there is an open issue to consider using viewport meta.


This spec repository is now moved to WICG github.