30 CSS Grid Layouts27 / 30
Scroll-Snap Slider
A native-feeling carousel with zero carousel library: a column-flow grid inside a snap container, slides centered by scroll-snap-align, momentum and touch physics free from the browser. Edge peeking shows there's more to scroll — the affordance JS sliders fake with padding math.
Published Updated
The code
<section class="cgl-27" aria-label="Scroll snap slider demo">
<div class="cgl-27__rail" role="group" aria-label="Featured slides">
<article class="cgl-27__slide" tabindex="0" style="--g:linear-gradient(130deg,oklch(0.55 0.16 330),oklch(0.3 0.1 300))"><h4>Native physics</h4><p>Momentum, rubber-band, touch — all free.</p></article>
<article class="cgl-27__slide" tabindex="0" style="--g:linear-gradient(130deg,oklch(0.5 0.15 260),oklch(0.28 0.09 290))"><h4>snap-align: center</h4><p>The browser settles each slide mid-view.</p></article>
<article class="cgl-27__slide" tabindex="0" style="--g:linear-gradient(130deg,oklch(0.55 0.14 200),oklch(0.3 0.08 240))"><h4>78% columns</h4><p>Neighbours peek — the honest 'more here' cue.</p></article>
<article class="cgl-27__slide" tabindex="0" style="--g:linear-gradient(130deg,oklch(0.6 0.13 150),oklch(0.32 0.08 180))"><h4>Zero libraries</h4><p>Delete the carousel dependency.</p></article>
</div>
</section><section class="cgl-27" aria-label="Scroll snap slider demo">
<div class="cgl-27__rail" role="group" aria-label="Featured slides">
<article class="cgl-27__slide" tabindex="0" style="--g:linear-gradient(130deg,oklch(0.55 0.16 330),oklch(0.3 0.1 300))"><h4>Native physics</h4><p>Momentum, rubber-band, touch — all free.</p></article>
<article class="cgl-27__slide" tabindex="0" style="--g:linear-gradient(130deg,oklch(0.5 0.15 260),oklch(0.28 0.09 290))"><h4>snap-align: center</h4><p>The browser settles each slide mid-view.</p></article>
<article class="cgl-27__slide" tabindex="0" style="--g:linear-gradient(130deg,oklch(0.55 0.14 200),oklch(0.3 0.08 240))"><h4>78% columns</h4><p>Neighbours peek — the honest 'more here' cue.</p></article>
<article class="cgl-27__slide" tabindex="0" style="--g:linear-gradient(130deg,oklch(0.6 0.13 150),oklch(0.32 0.08 180))"><h4>Zero libraries</h4><p>Delete the carousel dependency.</p></article>
</div>
</section>.cgl-27,
.cgl-27 *,
.cgl-27 *::before,
.cgl-27 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cgl-27 {
width: 100%;
min-height: 100vh;
--bg: oklch(0.14 0.02 310);
font-family: system-ui,sans-serif;
background: var(--bg);
padding: 1.6rem 0;
}
.cgl-27 .cgl-27__rail {
display: grid;
grid-auto-flow: column;
grid-auto-columns: 78%;
gap: 14px;
overflow-x: auto;
overscroll-behavior-x: contain;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
padding-inline: 11%;
padding-block-end: 10px;
scrollbar-width: thin;
scrollbar-color: oklch(0.5 0.1 310) transparent;
}
.cgl-27 .cgl-27__slide {
scroll-snap-align: center;
aspect-ratio: 16/8;
border-radius: 18px;
background: var(--g);
padding: 1.4rem;
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: .4rem;
border: 1px solid oklch(1 0 0/.14);
}
.cgl-27 h4 {
font-size: 1.1rem;
color: white;
}
.cgl-27 .cgl-27__slide p {
font-size: .8rem;
color: oklch(0.85 0.03 310);
}
.cgl-27 .cgl-27__slide:focus-visible {
outline: 3px solid oklch(0.8 0.14 330);
outline-offset: 3px;
}.cgl-27,
.cgl-27 *,
.cgl-27 *::before,
.cgl-27 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cgl-27 {
width: 100%;
min-height: 100vh;
--bg: oklch(0.14 0.02 310);
font-family: system-ui,sans-serif;
background: var(--bg);
padding: 1.6rem 0;
}
.cgl-27 .cgl-27__rail {
display: grid;
grid-auto-flow: column;
grid-auto-columns: 78%;
gap: 14px;
overflow-x: auto;
overscroll-behavior-x: contain;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
padding-inline: 11%;
padding-block-end: 10px;
scrollbar-width: thin;
scrollbar-color: oklch(0.5 0.1 310) transparent;
}
.cgl-27 .cgl-27__slide {
scroll-snap-align: center;
aspect-ratio: 16/8;
border-radius: 18px;
background: var(--g);
padding: 1.4rem;
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: .4rem;
border: 1px solid oklch(1 0 0/.14);
}
.cgl-27 h4 {
font-size: 1.1rem;
color: white;
}
.cgl-27 .cgl-27__slide p {
font-size: .8rem;
color: oklch(0.85 0.03 310);
}
.cgl-27 .cgl-27__slide:focus-visible {
outline: 3px solid oklch(0.8 0.14 330);
outline-offset: 3px;
}/* No JavaScript — a column-flow grid in a snap container; the browser supplies all carousel physics. */
/* No JavaScript — a column-flow grid in a snap container; the browser supplies all carousel physics. */Here's a working CSS Grid Layout from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Scroll-Snap Slider
Source: https://codefronts.com/layouts/css-grid-layouts/scroll-snap-slider/
A native-feeling carousel with zero carousel library: a column-flow grid inside a snap container, slides centered by scroll-snap-align, momentum and touch physics free from the browser. Edge peeking shows there's more to scroll — the affordance JS sliders fake with padding math.
## HTML
```html
<section class="cgl-27" aria-label="Scroll snap slider demo">
<div class="cgl-27__rail" role="group" aria-label="Featured slides">
<article class="cgl-27__slide" tabindex="0" style="--g:linear-gradient(130deg,oklch(0.55 0.16 330),oklch(0.3 0.1 300))"><h4>Native physics</h4><p>Momentum, rubber-band, touch — all free.</p></article>
<article class="cgl-27__slide" tabindex="0" style="--g:linear-gradient(130deg,oklch(0.5 0.15 260),oklch(0.28 0.09 290))"><h4>snap-align: center</h4><p>The browser settles each slide mid-view.</p></article>
<article class="cgl-27__slide" tabindex="0" style="--g:linear-gradient(130deg,oklch(0.55 0.14 200),oklch(0.3 0.08 240))"><h4>78% columns</h4><p>Neighbours peek — the honest 'more here' cue.</p></article>
<article class="cgl-27__slide" tabindex="0" style="--g:linear-gradient(130deg,oklch(0.6 0.13 150),oklch(0.32 0.08 180))"><h4>Zero libraries</h4><p>Delete the carousel dependency.</p></article>
</div>
</section>
```
## CSS
```css
.cgl-27,
.cgl-27 *,
.cgl-27 *::before,
.cgl-27 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cgl-27 {
width: 100%;
min-height: 100vh;
--bg: oklch(0.14 0.02 310);
font-family: system-ui,sans-serif;
background: var(--bg);
padding: 1.6rem 0;
}
.cgl-27 .cgl-27__rail {
display: grid;
grid-auto-flow: column;
grid-auto-columns: 78%;
gap: 14px;
overflow-x: auto;
overscroll-behavior-x: contain;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
padding-inline: 11%;
padding-block-end: 10px;
scrollbar-width: thin;
scrollbar-color: oklch(0.5 0.1 310) transparent;
}
.cgl-27 .cgl-27__slide {
scroll-snap-align: center;
aspect-ratio: 16/8;
border-radius: 18px;
background: var(--g);
padding: 1.4rem;
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: .4rem;
border: 1px solid oklch(1 0 0/.14);
}
.cgl-27 h4 {
font-size: 1.1rem;
color: white;
}
.cgl-27 .cgl-27__slide p {
font-size: .8rem;
color: oklch(0.85 0.03 310);
}
.cgl-27 .cgl-27__slide:focus-visible {
outline: 3px solid oklch(0.8 0.14 330);
outline-offset: 3px;
}
```
## JavaScript
```js
/* No JavaScript — a column-flow grid in a snap container; the browser supplies all carousel physics. */
```Here's a working CSS Grid Layout from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Scroll-Snap Slider
Source: https://codefronts.com/layouts/css-grid-layouts/scroll-snap-slider/
A native-feeling carousel with zero carousel library: a column-flow grid inside a snap container, slides centered by scroll-snap-align, momentum and touch physics free from the browser. Edge peeking shows there's more to scroll — the affordance JS sliders fake with padding math.
## HTML
```html
<section class="cgl-27" aria-label="Scroll snap slider demo">
<div class="cgl-27__rail" role="group" aria-label="Featured slides">
<article class="cgl-27__slide" tabindex="0" style="--g:linear-gradient(130deg,oklch(0.55 0.16 330),oklch(0.3 0.1 300))"><h4>Native physics</h4><p>Momentum, rubber-band, touch — all free.</p></article>
<article class="cgl-27__slide" tabindex="0" style="--g:linear-gradient(130deg,oklch(0.5 0.15 260),oklch(0.28 0.09 290))"><h4>snap-align: center</h4><p>The browser settles each slide mid-view.</p></article>
<article class="cgl-27__slide" tabindex="0" style="--g:linear-gradient(130deg,oklch(0.55 0.14 200),oklch(0.3 0.08 240))"><h4>78% columns</h4><p>Neighbours peek — the honest 'more here' cue.</p></article>
<article class="cgl-27__slide" tabindex="0" style="--g:linear-gradient(130deg,oklch(0.6 0.13 150),oklch(0.32 0.08 180))"><h4>Zero libraries</h4><p>Delete the carousel dependency.</p></article>
</div>
</section>
```
## CSS
```css
.cgl-27,
.cgl-27 *,
.cgl-27 *::before,
.cgl-27 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cgl-27 {
width: 100%;
min-height: 100vh;
--bg: oklch(0.14 0.02 310);
font-family: system-ui,sans-serif;
background: var(--bg);
padding: 1.6rem 0;
}
.cgl-27 .cgl-27__rail {
display: grid;
grid-auto-flow: column;
grid-auto-columns: 78%;
gap: 14px;
overflow-x: auto;
overscroll-behavior-x: contain;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
padding-inline: 11%;
padding-block-end: 10px;
scrollbar-width: thin;
scrollbar-color: oklch(0.5 0.1 310) transparent;
}
.cgl-27 .cgl-27__slide {
scroll-snap-align: center;
aspect-ratio: 16/8;
border-radius: 18px;
background: var(--g);
padding: 1.4rem;
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: .4rem;
border: 1px solid oklch(1 0 0/.14);
}
.cgl-27 h4 {
font-size: 1.1rem;
color: white;
}
.cgl-27 .cgl-27__slide p {
font-size: .8rem;
color: oklch(0.85 0.03 310);
}
.cgl-27 .cgl-27__slide:focus-visible {
outline: 3px solid oklch(0.8 0.14 330);
outline-offset: 3px;
}
```
## JavaScript
```js
/* No JavaScript — a column-flow grid in a snap container; the browser supplies all carousel physics. */
```How this works
The rail is grid-auto-flow: column; grid-auto-columns: 78% inside overflow-x: auto; scroll-snap-type: x mandatory. Each slide's scroll-snap-align: center makes the browser settle it mid-viewport — with 78% columns, the neighbours peek 11% on each side. All physics (momentum, rubber-banding, touch, trackpad, keyboard) is native scrolling, which no JS carousel replicates faithfully.
scroll-behavior: smooth upgrades anchor jumps if you add prev/next links; overscroll-behavior-x: contain keeps a swipe past the last slide from triggering browser back-navigation. Slides are focusable <article>s so keyboard users can Tab through — and because it's real scrolling, Tab automatically brings each slide into view.
Make it yours
- Peek amount = 100% − auto-columns %; 90% columns ≈ subtle 5% peeks.
- Snap start instead of center for gallery-style leading alignment (pair with scroll-padding-inline).
- Add prev/next as anchor links to slide ids — smooth-scrolled by the browser.
- Stack a dot indicator with scroll-driven animations (scroll-timeline) where supported — pure-CSS active dots.
Gotchas — read before shipping
- mandatory snap on content TALLER than the viewport traps vertical scrolling — for mixed-height slides use proximity.
- Don't hide the scrollbar without providing visible affordance (the peeks do that here); invisible scrolling fails discoverability.
- iOS momentum + mandatory snap occasionally skips a slide on hard flicks — acceptable native behaviour, don't fight it with JS.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 15.4+ | 113+ | 111+ |
Floor set by oklch() as this demo is written. The core technique itself goes back further — Chrome 69+.
Scroll snap has been interoperable since 2019 — this is among the most deployable demos in the set.