Performance & Design

CSS Scroll-Snap — Pinned Marketing Sections Without JavaScript

All articles
📌

Native CSS Pinning That Beats Wholesale JavaScript Libraries

Pinned sections — full-height layouts that snap into place as the user scrolls — have been a JavaScript burden for years. You pick a library (ScrollMagic, GSAP ScrollTrigger, Framer Motion), load 30–50kb of code, set up observers and timelines, and hope the browser doesn't stutter. There's a better way. CSS `scroll-snap-type: y mandatory` pins sections to the viewport without a single line of JavaScript, runs at native 60fps, and works in every modern browser. Velocity X uses this for the pinned pricing tiers on `/pricing` and the case-study showcase. It's a 4-property CSS solution that outperforms JavaScript-based pinning by an order of magnitude.

Why Scroll-Snap Beats JavaScript Pinning

JavaScript pinning libraries work by listening to scroll events, calculating offsets, and applying transforms or position changes to hold sections in place. This happens on every frame — 60 times a second — and threads through the main JavaScript thread, which also handles interactions, animations, and user input. If the user's device is busy or the library code is heavy, scroll becomes janky. The browser's native scroll-snap implementation runs on the GPU, bypasses the main thread entirely, and guarantees smooth 60fps snapping. Plus, it's progressive: browsers without scroll-snap support still scroll normally — no fallback library needed.

The Markup: Full-Height Sections with Snap Points

Wrap your sections in a vertical flex container with `scroll-snap-type: y mandatory`. Each full-height section gets `scroll-snap-align: start` and `height: 100vh` so the browser snaps the top of each section to the top of the viewport as you scroll. The container needs `overflow-y: auto` and `scroll-behavior: smooth` for buttery motion. That's it.

{`.scroll-container {
  display: flex;
  flex-direction: column;
  height: 100vh;
  overflow-y: auto;
  scroll-behavior: smooth;
  scroll-snap-type: y mandatory;
}

.pinned-section {
  height: 100vh;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  display: flex;
  flex-direction: column;
  justify-content: center;
}`}

The `scroll-snap-stop: always` property forces the browser to stop at each section instead of coasting past. On fast scrolls or trackpad momentum, users can't accidentally skip sections.

The Money Pattern: Layered Content Inside Pinned Sections

Each section is a full-height canvas. Stack content inside with flex or CSS grid — hero image, text overlay, call-to-action — and let the snap container handle the positioning. Velocity X layers a background image, a semi-transparent overlay, headline copy, and a button inside each section. When the user scrolls, the section snaps and stays centred until they scroll again. No JavaScript calculates offsets. No library manages state. The browser does it all.

{`.pinned-section {
  background: url('/case-study-hero.jpg') center / cover;
  position: relative;
}

.pinned-section::before {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
}

.pinned-section-content {
  position: relative;
  z-index: 10;
  max-w-2xl;
  text-white;
}

.pinned-section:nth-child(2) { background-image: url('/case-2.jpg'); }
.pinned-section:nth-child(3) { background-image: url('/case-3.jpg'); }`}

The Catch: Older Browsers and Non-Snappy Devices

Scroll-snap shipped in 2016, so IE11 is out. Most phones and desktops from 2018+ support it. If you need IE support, you'll still need a polyfill — but for everyone else (99% of traffic), the native implementation is bulletproof. And if scroll-snap fails, the page still scrolls; sections just don't pin. No broken experience, just a graceful fallback to normal scroll behaviour.

The Verdict

Scroll-snap is production-ready and faster than any JavaScript alternative. If you're pinning sections on a marketing site, stop reaching for ScrollTrigger and start with CSS. See the full pattern in Velocity's `/pricing` page, then port it to your site. The performance win is immediate and the code weight saved is permanent.

Let us make some quick suggestions?
Please provide your full name.
Please provide your phone number.
Please provide a valid phone number.
Please provide your email address.
Please provide a valid email address.
Please provide your brand name or website.
Please provide your brand name or website.