Scroll snapping is still underused outside carousels

CSS Scroll Snap has been supported in all modern browsers for over two years, yet most sites that could benefit from it still don't use it. The feature is typically associated with horizontal carousels or full-screen slide decks, but snapping can improve the scrolling experience on any page that lays out items in a grid or feed.

Shopping sites are a good example. Products are displayed in rows, and ideally the user jumps between those rows with minimal effort. Pressing Space scrolls by roughly a viewport height, but depending on row height the scroll position drifts out of sync with the grid and requires manual correction. Adding scroll snapping means each Space press reliably lands on the next row, with Shift + Space going back to the previous one. The CSS for this is straightforward:

html {
  scroll-snap-type: y proximity;
}

.product-item {
  scroll-snap-align: start;
  scroll-margin-top: 75px; /* height of web page’s sticky header */
}

If a site you visit regularly hasn't added snapping, you don't have to wait for it. You can add it yourself with user styles.

How to inject user styles

Safari's advanced preferences let you select a local .css file that is applied to every page you open. Chrome and Firefox have no equivalent: Firefox's userContent.css was deprecated and disabled by default in 2019. For those browsers, the Stylus browser extension is the recommended route, and it has the added benefit of targeting styles to specific URLs.

There is an important cascade distinction between these two approaches. Safari's user style sheet belongs to the CSS Cascading module's User Origin. Stylus, however, injects styles into the Author Origin—the same origin as the website's own CSS—by appending a <style> element to the end of <html>, making it the final style sheet on the page.

The Stylus extension has been reviewed by both Chrome and Firefox teams and received a badge that denotes high standards.

In practice, this means that when selector specificity is equal, a genuine user style is weaker than the page's own style, which makes it well-suited for setting user defaults. A Stylus-injected style, by contrast, is stronger under the same conditions. Adding !important to either, however, beats the page's styles in both cases, which is what you want when imposing your own styles on a website.

Applying snapping to Twitter's timeline

The goal was to make reading the Twitter timeline faster by eliminating the awkward position where the topmost tweet is only partially visible. The following styles work well in Firefox; Chrome and Safari have issues described below.

html {
  scroll-snap-type: y mandatory !important;
}

/* tweets in the timeline are <article> elements */
article {
  scroll-snap-align: start !important;
}

/* un-stick the sticky header and make it “snappable” as well */
[aria-label="Home timeline"] > :first-child {
  position: static !important;
  scroll-snap-align: start !important;
}

/* hide the “new Tweets available” floating toast notification */
[aria-label="New Tweets are available."] {
  display: none !important;
}

Every declaration needs !important because the user styles must win over Twitter's own CSS. Ideally this would be done with an "important layer," but such a CSS feature does not exist yet.

The result is that each Space press brings the next set of tweets into view with the first tweet of each set aligned to the top edge of the viewport. Shift + Space scrolls back up. What makes this predictable is that the scroll distance is always the combined heights of the visible tweets that are entirely on screen; the partially visible tweet at the bottom moves to the top.

I know in advance that pressing Space will scroll Dave’s tweet to the top of the screen.

To try it yourself on https://twitter.com/home:

  1. Install the Stylus extension from Firefox Add-ons or the Chrome Web Store.
  2. Click the Stylus icon in the browser toolbar, then click "this URL" in the pop-up.
  3. Copy and paste the user styles into the editor that opens in a new tab, and press Save. The styles apply immediately—no page reload needed.
  4. To edit later, click the Stylus icon and then the pencil icon.

The tall-tweet problem with mandatory snapping

The main flaw is that if a tweet is taller than the viewport, you cannot scroll to reveal its bottom portion—to like or retweet it, for example—because the browser forcefully snaps the page to the top of the tweet or the following one. The severity depends on the display: on a large desktop monitor at a small zoom factor, it may not occur at all.

I've asked the CSS Working Group whether a mechanism could be added to let users override mandatory snapping. Switching from mandatory to proximity is theoretically a solution, but in testing across Chrome and Firefox, proximity behaved inconsistently—snapping when unexpected and failing to snap when expected. It's unclear whether that's a browser bug, an interaction with Twitter's code, or user error.

Mandatory snapping was chosen because it guarantees the timeline never lands with a partially visible tweet at the top, which is what makes fast, predictable scrolling possible. Two workarounds exist for tall tweets:

  • Open the tweet on its own page and return to the timeline afterward.
  • To like or retweet without scrolling, Shift-click the tweet to select it, then press L to like or T then Enter to retweet.

Browser-specific behavior and bugs

Snapping behaves noticeably differently across Chrome, Safari, and Firefox. Some of this is by design—the CSS Scroll Snap specification explicitly leaves the exact snapping physics to the user agent.

The CSS Scroll Snap Module intentionally does not specify nor mandate any precise animations or physics used to enforce snap positions; this is left up to the user agent.

Safari currently has a bug that prevents scroll snapping from working at all on the Twitter timeline, which has been reported. Chrome works but has its own issues:

  • Scrolling animations are inconsistent: sometimes slow, sometimes instant, sometimes cut short mid-animation.
  • Scrolling is generally too slow. In a test of 20 Space presses, covering the same distance on the timeline took 18.5 seconds in Chrome versus 11 seconds in Firefox—a 70% difference.
  • Trackpad scrolling causes flickering, and holding down Space to scroll fast produces very slow, oscillating scroll instead. The suspicion is that Chrome re-snaps at an excessively high rate in these cases. A bug report has been filed.

These cross-browser inconsistencies can discourage sites from adopting scroll snapping. Browser vendors can help through interoperability efforts; Scroll Snap is in fact one of the focus areas of the Interop 2022 project. New CSS properties for snapping configuration—animation duration, proximity threshold length, and a way to override mandatory snapping—could also improve the situation.

Is snapping right for your feed?

After weeks of daily use, the scroll snap styles on the Twitter timeline feel essential. Flipping through the feed with just the Space key is a genuinely better reading experience. But it's an advanced feature that isn't for everyone, which is why it was enabled only on the /home path and nowhere else on the site. Snapping fundamentally changes how a page scrolls and takes getting used to; it can help for a specific use case but can also frustrate users in others.

Sites with feeds should treat snapping as an optional feature, offered only after careful testing across browsers, input methods, and viewport sizes. Finally, the Stylus extension itself is worth installing even without scroll snapping in mind—it lets anyone who knows CSS apply minor fixes and improvements to any site, from hiding sticky headers to testing new CSS features and filing browser bugs along the way.