Skip to content
Open UI

Declarative Overscroll Actions (Explainer)

Authors
@vmpstr, @flackr, @mfreed7
Created
Last Updated

Introduction

The web platform allows for sophisticated scrolling experiences, but it currently lacks a semantic way to utilize “overscroll” space (the area beyond the scroll boundary).

Common UI patterns like drawer-menus (swiping past the edge to reveal a menu) currently rely on complex nested scrollers or JavaScript gesture polyfills. These workarounds are difficult to implement, computationally expensive, and often fail to provide accessible alternatives for non-touch users.

Proposal

We propose a set of HTML attributes and CSS properties that declaratively bind an element (the “overscroll content”) to the scroll boundary of a container.

Crucially, this binding is defined both semantically on the participating elements (overscroll container and overscroll area) as well as on an activatable element (like a <button>). This ensures that the menu set up is both semantically represented in DOM and that every gesture-based action has a guaranteed accessible fallback interaction (click/Enter) on the activatable element without extra developer effort.

Goals

  • No JavaScript: Enable swipe-to-reveal gesture based menu interactions using only HTML and CSS.
  • Accessibility by Default: Enforce the existence of a semantic button to toggle the view, ensuring keyboard and assistive technology support.
  • Performance: Offload gesture physics and animation to browser’s control, ensuring possibility of composited gestures.

The API

There are several parts to the API:

First, the container needs to be identified as supporting overscroll areas. This is done by specifying overscrollcontainer attribute on the container.

Second, the overscroll area (e.g. the side menu element), which must be a direct child of an overscroll container, is identified as the overscroll area. This is done by specificying overscrollarea attribute on the container.

Finally, we introduce a three new command values, to bind a trigger button to the overscroll area using commandfor attribute:

  • show-overscroll - this invoker opens the referenced overscroll area
  • hide-overscroll - this invoker closes the referenced overscroll area
  • toggle-overscroll - this invoker toggles the state of the overscroll area. If the area is open, then this will close it. Conversely, if the area is closed, then this will open it.

Note that either the show-overscroll invoker (a.k.a. show invoker) or the toggle-overscroll invoker (a.k.a. toggle invoker) must be present in order for this structure to be recognized as an overscroll structure. The overscrollcontainer and overscrollarea attributes on their own are not sufficient. Also note that the hide-overscroll invoker (a.k.a. hide invoker) is neither sufficient nor required for this structure. The rationale is that since the ::overscroll-backdrop already acts as a hide invoker, we don’t require a developer specified one. The developer is, of course, free to provide one as a way to express specific user experience.

See the discussion on inertness for justification for three different commands.

<div id="container" overscrollcontainer>
    <menubar id="menu" overscrollarea>
        <menuitem>Home</menuitem>
        <menuitem>Settings</menuitem>
    </menubar>
    Some content.
</div>

<button commandfor="menu" command="toggle-overscroll" id=btn>
  Toggle Menu
</button>

Behavior

  1. Positioning: the #menu is effectively absolutely positioned within the “overscroll” area of the overscroll container.
  2. Chaining: If a user scrolls #container to its limit, the scroll chains to the #menu, pulling it into view. Note that the #container does not have to be a scroller for this effect.
  3. Activation: As an alternative to scroll gestures, activating the <button> will perform a scrollIntoView-like action on the #menu. Activating it again scrolls #menu back out of view.

(Here is a simple demo implementation on Github.io. This demo does not require any browser features.)

Terminology

In this explainer, we’ll use the following terminology:

  • overscroll container: the scrolling container with the overscrollcontainer attribute. #container in the above code snippet.
  • overscroll area: the element within the overscroll container that gets rendered as overscrolled content. #menu in the above code snippet.
  • “overscroll invoker”: the command invoker with one of the new command values pointing to the overscroll area. #btn in the above code snippet.

Nesting and Structure

  • Overscroll invoker: There is no special requirements for the position of the overscroll invoker relative to the overscroll container or area. It can live anywhere in the tree scope, subject to the normal rules for command invoker relationships.
  • Overscroll area: The overscroll area element must be a direct child of the overscroll container. It can also have other content before or after it. Note that the overscroll area element will typically be taken out of flow, as if it is position: absolute and contained by the overscroll container.

Chaining and Ordering

An overscroll container can have multiple overscroll areas (e.g., one on each side). The ::overscroll-area-parent manages these as siblings.

  • LIFO Order: Chaining follows a “last-in, first-out” order. The last overscroll area in DOM order within the overscroll container is chained, and scrolls, first.
  • Chaining Path: When the innermost scroller reaches its limit, it chains to the next overscroll area, and so on.

Configuring the style of overscroll area.

By default, overscroll area pushes the container’s content. This is controlled by a new css property overscroll-container-type:

overscroll-container-type: auto | push | overlay | none

  • auto: The default behavior, currently same as push but this is subject to discussion.
  • push: The overscroll areas of this overscroll container will push other content out of the way.
  • overlay: The overscroll areas of this overscroll container will overlay on top of other content.
  • none: This precludes the overscroll container from acting as an overscroll container. Note that this is a CSS “opt-out” of the behavior, which is useful for use in media queries to toggle overscroll container feature without needing to modify the semantic delcarations in HTML.

Note that this is a property of the overscroll container, not overscroll area, for simplicity of API design. It therefore affects all overscroll areas within that container. If use cases arise that require different overflowing elements to have different overlay modes, we can revisit this. But that does not seem to be a common use case today on the web.

Events

To allow developers to hook into the lifecycle of the gesture (e.g., for refresh logic or haptics), we expose the following events on the host container:

Event NameDescription
overscrollstartFired when the scroll boundary is breached and chaining begins.
overscrollchangingFired when the gesture sufficiently drags overscroll to snap it to an open area (similar to scrollsnapchanging).
overscrollendFired when the gesture completes and the state has changed.
overscrollcancelFired when the gesture ends but snaps back to the original state.

Backdrop

When the overscroll area is visible, the overscroll area has an ::overscroll-backdrop pseudo element that is styled by default with a semi-transparent cover, similar to a dialog ::backdrop. This includes a default button handler to dismiss, or close, the corresponding overscroll area.

This backdrop is always present but has a display: none style when the overscroll area is closed. The timing of when the display value changes aligns with the overscrollchanging event.

Use case examples

Drawer menu

A hidden navigation panel that typically slides in from the edge of the screen when triggered by a button, such as a hamburger menu icon. It allows users to access site-wide navigation, settings, or profile information without permanently cluttering the primary view.

Behaviors:

  • Light dismissable: yes
  • Modal: yes
A drawer menu mockup,
with a side menu that can be opened with a swipe
<button commandfor="drawer-menu" command="toggle-overscroll">
  ☰ Menu
</button>
<div id="app-layout" overscrollcontainer="overlay">
  <dialog id="drawer-menu" closedby="any">
    <nav>
      <li><a href="/">Home</a></li>
      <li><a href="/profile">Profile</a></li>
    </nav>
  </dialog>
  <main>Primary view content...</main>
</div>

Implementation Model

Note: This section details the conceptual rendering tree structure.

When configured, the browser constructs an internal box structure to handle hit-testing and painting order:

Box Structure Diagram

  1. .container creates an internal ::overscroll-area-parent. This is not affected by scrolling container, and scroll chains to ::overscroll-area-parent after container.
  2. ::overscroll-area-parent contains the menu element. As a result, scrolls targeting the menu element chain directly to the ::overscroll-area-parent, which can bring the typically-offscreen menu into view.

Overscroll Animation

Modalness, Inertness and Light Dismiss

Note that the side menus, as supported by this feature, are modal to the container in which they reside. Specifically, it means that when the menu is open, the content underneath is not meant to be interacted with.

ISSUE: In the future, we may add other overscroll-container-type values that support cooperative, non-modal side menus as well. For now, if the behavior needs to change on, for example, wide screens, it can be accomplished with media queries and disabling the overscroll feature. This would make the side menu a regular element in the page.

Being modal implies that, when closed, the menu is not focusable or interactable. For this reason, the overscroll area element that is closed is inert. Conversely, when the overscroll area is open, the regular content that it pushes out of the way or overlays is inerted.

Note that the backdrop acts as a light dismiss signal for this container. It has a default click handler that dismisses the overscroll area.

Note that focus navigation also behaves as if the menu is modal: when traversing the menu, the main content is not visible. This is a consequence of content being inert but worth calling out explicitly.

It is worth mentioning that the invokers for a given overscroll area get special treatment:

  • toggle-overscroll: this invoker is never inerted by the behavior of the related overscroll area. This allows the developer to, for example, positon it in the overscroll area but peaking into the content area as a toggle button, which will always remain in the interactive set.
  • show-overscroll / hide-overscroll: these invokers are not treated any differently from the regular content or overscroll area and are subject to the same rules as outlined above. This means that if, for example, a show invoker is placed in a closed overscroll area and is peeking, it would be inert. This is considered a developer error: they should use the toggle invoker for this case.

Note that special consideration needs to be taken for multiple overscroll areas on the same container. This should follow the stacked order: overscroll areas that are on top of other overscroll areas will inert everything underneath them including the invokers for the areas underneath them. The above description of invoker treatment only applies to the overscroll area referenced in the commandfor attribute of the invoker.

ISSUE: Whether or not the menu automatically dismisses when focus leaves the menu remains an open question.

Accessibility Considerations

We think this proposal solves a major accessibility hurdle in gesture UIs. By attaching the behavior to a <button>:

  • Keyboard Users: Can tab to the button and activate it to reveal the menu/action.
  • Screen Readers: Perceive a standard button connection rather than an invisible gesture zone.
  • Discoverability: The button provides a visible affordance for the action.
  • Mitigation: Because the invoking element and the content both live in HTML, standard aria-* attributes can be used for any necessary accessibility mitigations, as needed.

Focus Management

The overscroll area functions similarly to a popover with respect to focus. Activating the overscroll invoker button should not move focus to the menu automatically, but the menu should be next in the focus order.

Furthermore, interactive elements within the overscroll area should not be in the sequential focus navigation order (the tab order). WCAG 3.2.1 Understanding “On Focus” says that focus shouldn’t “change the context”. If there are interactive elements in the overscroll area that remain focusable while the area is closed, then focusing them would need to scroll them into view (required by WCAG 2.4.11 “Focus Not Obscured”), and that would activate the overscroll area. Typically, overscroll areas such as a drawer menu represent a context change, so this would be bad.

Note that the presence or absence of the invoker in the focus order is dictated by its inertness, as discussed above.

Progressive Enhancement

For browsers that do not support this feature, the content simply becomes an inline part of the ordinary scroller. This likely presents as a broken experience.

A polyfill strategy might be to switch the overscroll content to a popover and switch the command invoker to command=toggle-popover. This would preserve the semantic relationship and a11y, while providing a functional fallback.

Interaction with Browser Gestures

Many browsers provide built-in overscroll behaviors, such as “pull-to-refresh” (reload) or “swipe-to-navigate” (back/forward).

These behaviors continue to function as-is, since the overscroll area is simply a chained scroller. Once the overscroll area is scrolled fully into view, the scroll will chain to the viewport scroller, and will activate these browser- based overscroll actions.

Alternatives Considered

CSS-only Properties

We considered defining this relationship purely in CSS. While powerful, CSS lacks the semantic enforcement of an interactive element. A CSS-only solution runs the risk of creating “invisible” gestures that are inaccessible to users who cannot perform swipe actions. By requiring an HTML activator, we enforce progressive enhancement.

HTML-only Attributes

We considered having all of the features specified in HTML. Expressing semantic relationships makes sense in HTML, but whether the content is pushed or overlays or displays as a non-overscroll menu seems like a presentational decision that is commonly gated by a media query. For this reason, the configuration of the type of overscroll behavior is controlled from CSS.

Open questions

  • ARIA Attributes: Should the browser automatically handle aria-expanded and aria-controls on the overscroll invoker button based on the visibility of the overscroll content, in the same way that popovers are handled?
  • Visibility: When not revealed, should the overscroll content be treated as aria-hidden="true"? Seems like yes.
  • Activatable Elements: Are there any interactive elements (like <area>) that should not be allowed as overscroll invokers?
  • Pseudo Classes: Should the :open pseudo class match the overscroll area when it is scrolled into view? Or perhaps a new pseudo class?