Scroll✨ Premium

Horizontal Scroll

Six 200 × 200 px tiles — a 32 px number and a short label — in a flex track with a 12 px gap that scroll-snap-type: x mandatory stops with a tile centered, a 212 px pitch, in CSS alone (zero bytes of JavaScript). Measured on 524 px of width: one horizontal wheel notch or a 150 px swipe advances one tile; the first and last tiles never center (±146 px); beyond 1,292 px of container nothing scrolls anymore while the “Scroll →” hint keeps pulsing.

CSSHorizontalSticky

You'll get access to the interactive demo with a free account.

This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Scroll has 70 effects, including 12 free. Explore the category →

3 usage examples

Product families of a mobile catalog — 01 Tools, 02 Fasteners, 03 Safety… — Horizontal Scroll example 1

① Product families of a mobile catalog — 01 Tools, 02 Fasteners, 03 Safety…

WhenMobile catalog page of a retailer: search bar, strip of numbered families, results grid below that follows the centered family.
WhyShort numbered labels give the navigation order at a glance, with three or four families visible at once. Horizontal Scroll (fx-0541), colored blocks without text, cannot name families; Horizontal Snap (fx-0594), full-width, shows only one at a time and loses the overview.
SettingsTiles reduced to 128 × 92 px, 22 px number, track gutters of calc(50% - 64px) so the first and last families center too; a scene script finds the tile closest to the center (is-active class, aria-current) and shows the matching grid; light background, “Scroll →” hint removed.
Chapter selector of an online course — 01 Introduction, 02 Foundations, 03 Practice, 04 Assessment — Horizontal Scroll example 2

② Chapter selector of an online course — 01 Introduction, 02 Foundations, 03 Practice, 04 Assessment

WhenLearning app: course title and progress, chapters scrolling horizontally, the selected chapter's content (lessons, durations, button) loaded under the selector.
WhyA chapter has no illustration: a numbered title is enough, and the centered tile is the current chapter. Card Snap Carousel (fx-0595) — 200 × 240 px cards with an image area, a paragraph and a tag — would be oversized for a plain selector.
Settings150 × 104 px tiles, 26 px number, 2 px white ring on the centered chapter, chapter panel switched by the same centered-tile detection; automatic advance of one tile every 560 ms for the demonstration.
Marketing campaign KPIs, mobile dashboard — 01 ROAS, 02 CPC, 03 CTR, 04 CVR, 05 CAC — Horizontal Scroll example 3

③ Marketing campaign KPIs, mobile dashboard — 01 ROAS, 02 CPC, 03 CTR, 04 CVR, 05 CAC

WhenDashboard checked on a phone: the five main KPIs in a row, each with its value and delta, and the last-7-days chart of the centered KPI.
WhyScrolling is free and stops on each KPI: you read an exact figure without hurry. Sticky Horizontal (fx-0578) scrolls its panels by itself over 12 s — impossible to read a value at your own pace.
SettingsFive 164 × 124 px tiles aligned left (align-items: flex-start), number reduced to 15 px, spaced uppercase label, 28 px value and delta added inside the tile with b and small elements (the .ss-horiz-item span rule only targets the number); bar chart switched under the strip.

How it works

Three levels and not one line of script. .ss-horiz-content is the viewport: height: 280px, overflow: auto hidden (horizontal scrolling, vertical cut off), display: flex with align-items: center, and above all scroll-snap-type: x mandatory. Inside, .ss-horiz-track lines up six .ss-horiz-item in flex with gap: 12px and padding: 20px 16px. Each tile: flex-shrink: 0, 200 × 200 px, border-radius: 14px, centered column, label at .9rem (14.4 px) bold, a span for the number at 2rem (32 px), weight 800, opacity: .5, and scroll-snap-align: center. Six 135° gradients via :nth-child (indigo → violet → fuchsia → rose → amber → green → cyan). The track measures 6 × 200 + 5 × 12 + 32 = 1,292 px. No CSS variable, empty js: the scrolling is the browser's own, on the compositor, with no listener.

Measured in Chromium on a 524 px viewport: scrollWidth 1,292, maximum travel 768 px. The rest positions are the tile centers minus half the viewport: 66, 278, 490, 702 — a 212 px pitch (200 + 12). Tile 1 would need −146 px and tile 6 +914: the bounds clamp them to 0 and 768, so neither the first nor the last tile ever centers; they stay 146 px off. mandatory forbids any in-between position: a scrollLeft forced to 50 settles at 66, 320 → 278, 900 → 768. One horizontal wheel notch (deltaX 120) advances exactly one tile (66 → 278 → 490 → 702 → 768); the vertical wheel does nothing (0 px); without JavaScript, dragging with the mouse does nothing either — trackpad, touch or the scrollbar only. On an emulated iPhone 13 (374 px), a 150 px swipe gives 141, 353, 565, 777 (same 212 px pitch), a 40 px swipe goes back to 0. No scroll-behavior: smooth: writing scrollLeft jumps; scrollTo({ behavior: 'smooth' }) glides (0 → 490 px in ~320 ms) and the snap finishes the move.

What the code leaves unsettled. The viewport has no width of its own: as a flex child of the sold .demo-preview (centered), it shrinks to the parent's width because a scroll container has min-width: 0; in a block parent it takes 100%. As soon as the parent exceeds 1,292 px, the whole track fits, centered, and nothing scrolls anymore (measured at 1,400 px: travel 0); at 1,264 px a 28 px stub remains between two snap positions. In both cases .sdc-scroll-hint — “Scrollez →”, an sdc- prefix shared with the catalog's other scroll demos, an opacity pulse .3 ↔ .7 over 2 s — keeps blinking. Nothing marks the centered tile (no active state), and the sold HTML mixes English labels (Design, Develop, Test…) with a French hint. The catalog's “Sticky” tag matches nothing in the code: the sticky, self-animated variant is Sticky Horizontal (fx-0578).

Accessibility

  • prefers-reduced-motion is absent from the code — and, for once, almost without consequence: scrolling is only ever triggered by the user (no scroll-linked animation, no automatic motion). Only the hint's pulse ignores it: measured under reducedMotion: reduce, its opacity still swings from 0.34 to 0.70 within one second. Add @media (prefers-reduced-motion: reduce) { .sdc-scroll-hint { animation: none; opacity: .5 } }, or drop the hint when integrating.
  • Keyboard: .ss-horiz-content has no tabindex. Chrome 130+ makes any scroll container without focusable children focusable — measured: Tab stops on it and → advances one tile (scrollLeft 0 → 66) —, Firefox and Safari do not. Set tabindex="0" and an aria-label on the viewport. If the tiles act as a selector (families, chapters), they are divs: replace them with buttons or links and manage aria-current on the one driving the content.
  • Insufficient contrast on five tiles out of six (white 14.4 px bold label, 4.5:1 threshold): tile 1 indigo 4.4:1, tiles 2 and 3 between 3.5 and 3.9:1, tiles 4 to 6 (rose → amber → green → cyan) between 2.2 and 2.9:1. The number at opacity: .5 falls between 1.5 and 2.2:1. Fix it with darker gradient stops (#b45309, #15803d, #0e7490 in place of the amber, green and cyan), a text-shadow: 0 1px 2px rgba(0,0,0,.35), or a label of at least 18.7 px bold (large text, 3:1 threshold).
  • Screen readers and integration: six divs read as “01 Design”, “02 Develop”… in order, with no role and no announcement of the centered tile — the scroll position carries no semantics. Touch works natively (one tile per swipe, a short swipe settles back). For the first and last tiles to center like the others, give the track padding: 20px calc(50% - 100px) (which is what the three scenes do); to avoid misleading users on wide screens, cap the viewport (max-width) or hide the hint when scrollWidth does not exceed clientWidth.

Browser compatibility

Stylesheet only, no JavaScript: scroll-snap-type: x mandatory and scroll-snap-align: center (2018 syntax), two-value overflow (auto hidden), flexbox gap, linear gradients. Scrolling is native: the effect works in Firefox, unlike CSS scroll-driven animations (animation-timeline), which Firefox does not enable in any version.

Chrome 84+✓ Full
Firefox 68+✓ Full
Safari 14.1+✓ Full
Edge 84+✓ Full
Mobile iOS 14.5+✓ Full (one tile per swipe)
Android Chrome✓ Full (one tile per swipe)

Without scroll-snap (older browsers), the track scrolls freely and stops anywhere, but everything stays visible and readable. Without flexbox gap (Safari 13 and earlier), the tiles touch and the pitch drops from 212 to 200 px. Nothing depends on a script: there is none.

The code

HTML structure to paste into your page (CSS + JS available with a premium account):

index.html — structure
<div class="ss-horiz-content">
  <div class="ss-horiz-track">
    <div class="ss-horiz-item">
      <span>01</span> Design
    </div>
    <div class="ss-horiz-item">
      <span>02</span> Develop
    </div>
    <div class="ss-horiz-item">
      <span>03</span> Test
    </div>
    <div class="ss-horiz-item">
      <span>04</span> Deploy
    </div>
    <div class="ss-horiz-item">
      <span>05</span> Monitor
    </div>
    <div class="ss-horiz-item">
      <span>06</span> Iterate
    </div>
  </div>
</div>
<span class="sdc-scroll-hint">Scroll →</span>
🔒 Unlock the full code — from €2.99 the first month

Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Tile size (CSS — .ss-horiz-item width / height) 200 × 200 px The snap pitch is width + gap, i.e. 212 px: the distance of one wheel notch or one swipe. Visible tiles = viewport width ÷ 212 (two and a half at 524 px). The scenes go down to 128 × 92 px without changing anything else.
Gap (CSS — .ss-horiz-track gap) 12px Adds to the snap pitch. At 0 the tiles touch and the pitch becomes 200 px; at 24 the gradients breathe but one fewer tile fits on screen.
Track gutters (CSS — .ss-horiz-track padding) 20px 16px With 16 px, the first and last tiles stop 146 px from the center. padding: 20px calc(50% - 100px) (half the viewport minus half a tile) centers them like the others — the setting used by all three scenes.
Snap strictness (CSS — .ss-horiz-content scroll-snap-type) x mandatory x proximity only realigns near a tile and allows stopping in between — preferable if tiles are wider than the viewport (mandatory would make the overflow unreachable). none: free scrolling, the effect disappears.
Alignment point (CSS — .ss-horiz-item scroll-snap-align) center start aligns the tile on the left edge: the first one then naturally rests at 0, and the left padding stays visible through scroll-padding-left: 16px on the viewport.
Number of tiles and colors (HTML + CSS — :nth-child(1) to (6)) 6 gradients at 135° A seventh tile has no background: add an :nth-child(7) rule or move the gradients to inline style as fx-0541 does. The track grows by 212 px per tile.
Viewport height (CSS — .ss-horiz-content height) 280px The track (200 + 2 × 20 = 240 px) is centered in it. Reduce it along with the tiles: 118 px for 92 px tiles in the catalog scene.
Programmatic scrolling (CSS — scroll-behavior, absent) auto (instant jump) For a “next” button or a scrollLeft = … to glide, add scroll-behavior: smooth on the viewport, or call scrollTo({ left, behavior: 'smooth' }) — measured: 0 → 490 px in ~320 ms, the snap finishes the move.

FAQ

Because scroll-snap-align: center asks for a negative scroll position for the first (center at 116 px, half viewport 262 px: −146) and one beyond the travel for the last (+914 px for 768 of travel). The browser clamps those positions to the 0 and 768 bounds: both tiles stop 146 px from the center, measured on 524 px. Two fixes: track gutters equal to half the viewport minus half a tile (padding: 20px calc(50% - 100px)), or start alignment with scroll-padding-left. Without it, an “active tile = closest to center” script will never select the first or the last.
That is the code as is. The viewport has no maximum width; the track is 1,292 px. Once the container reaches that width, everything is visible, the travel drops to 0 (measured at a 1,400 px viewport) and the hint keeps pulsing since it is only an opacity animation. Between 1,200 and 1,292 px, a few dozen pixels of travel remain (28 px at 1,264): the tile merely shivers between two stops. Set a max-width on .ss-horiz-content (720 px = three centered tiles), or hide the hint when scrollWidthclientWidth — one line of JavaScript, the only one the effect would ask for.
The structure is identical (viewport, track, six blocks, centered snap, same hint), but fx-0541 shows 180 × 140 px blocks with a single digit and inline style gradients: thumbnails, not labels. fx-0586 adds a short label under a half-transparent number in a square 200 px tile: it is the “named steps or families” version. The lot's other neighbors answer other needs — Horizontal Snap (fx-0594): full-width panels one at a time; Card Snap Carousel (fx-0595): 200 × 240 px cards with an image area, text and a tag, plus scroll-behavior: smooth; Sticky Horizontal (fx-0578): automatic scrolling over 12 s, with no user control.