Some of the most app-like effects on the web come from scrolling. Swipe through a row of product cards on a phone and each card settles neatly into place. Click "Back to top" and the page glides up instead of jumping. These used to need JavaScript slider libraries; now CSS does them natively with scroll snap and smooth scrolling, and they feel better because they use the device's own scrolling physics.
This lesson also covers a quieter but useful layout tool: multi-column layout. With one property, columns, long text or a list of short items flows into newspaper-style columns that adjust to the screen width.
You'll build a swipeable carousel with no JavaScript, make in-page links scroll smoothly and land below a sticky header, and create responsive column layouts for text, link lists and masonry-style galleries. These features build on overflow and flexbox, which you already know.
Scroll snap basics
.carousel {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
}
.carousel > * { scroll-snap-align: start; flex: 0 0 80%; }- On the scroll container,
scroll-snap-typesets the direction (x,yorboth) and the strictness. mandatoryalways snaps to an item when scrolling stops.proximityonly snaps when the user stops close to one, which is gentler for long content.- On each child,
scroll-snap-alignchooses which edge snaps:start,centerorend. scroll-snap-stop: alwaysstops a fast swipe from skipping past items.
Polishing a carousel
| Property | Use |
|---|---|
scroll-padding | Leaves space at the edge when snapping, for example to match page padding |
gap | Space between slides |
flex: 0 0 80% | Each slide is 80% wide, so the next one peeks in and invites a swipe |
overscroll-behavior-x: contain | Stops a sideways swipe from triggering browser back navigation |
scrollbar-width: none | Hides the scrollbar (keep another visible cue, such as the peeking slide) |
Make carousels accessible: give the container tabindex="0" and an aria-label so keyboard users can focus and scroll it with the arrow keys, and never hide important content only in later slides.
Smooth scrolling and scroll-margin
html { scroll-behavior: smooth; }
h2 { scroll-margin-top: 80px; }scroll-behavior: smoothanimates jumps to#anchorlinks and JavaScript calls likescrollTo().- If your site has a sticky header, linked headings land underneath it.
scroll-margin-topon the targets adds space so they stop just below the header. - Wrap smooth scrolling in
@media (prefers-reduced-motion: no-preference)so people who prefer less motion get instant jumps.
Multi-column layout
.article { columns: 3 220px; column-gap: 32px; column-rule: 1px solid #e5e7eb; }columns: 3 220pxmeans "up to 3 columns, each at least 220px wide". On a phone you get one column, with no media query.column-gapandcolumn-ruleset the space and an optional line between columns.break-inside: avoidon items stops a card or paragraph splitting across two columns.column-span: alllets a heading stretch across every column.
Columns read top to bottom then left to right, so they suit short items such as link lists, tags and photo galleries. For long articles, scrolling down to the end of one column and back up to the next is awkward, so use them carefully.
CSS Scroll Snap Syntax
Scroll and columns
.track { display: flex; gap: 12px; overflow-x: auto; scroll-snap-type: x mandatory; scroll-padding: 16px; }
.slide { flex: 0 0 80%; scroll-snap-align: start; }
@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } }
:target, h2 { scroll-margin-top: 80px; }
.links { columns: 3 180px; column-gap: 24px; }
.links li { break-inside: avoid; }CSS Scroll Snap Summary
scroll-snap-typeon the container andscroll-snap-alignon children make items settle into place.- A flex row with
overflow-x: autoand snap points is a carousel with no JavaScript. scroll-behavior: smoothanimates anchor jumps; respect reduced motion.scroll-margin-topstops linked headings hiding under sticky headers.columns: 3 220pxcreates responsive multi-column layouts; usebreak-inside: avoidon items.
CSS Scroll Snap Examples
Each example runs in the page. Edit the code and press Run to see what changes.
A swipeable carousel with no JavaScript
Scroll or swipe sideways. Each card snaps into place.
<div class="track" tabindex="0" aria-label="Featured recipes">
<article class="slide">Lemon pasta</article>
<article class="slide">Thai green curry</article>
<article class="slide">Shakshuka</article>
<article class="slide">Mushroom risotto</article>
</div>.track {
display: flex;
gap: 12px;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-padding: 0 12px;
padding: 0 12px 10px;
overscroll-behavior-x: contain;
}
.slide {
flex: 0 0 75%;
scroll-snap-align: start;
height: 140px;
display: grid;
place-items: center;
background: linear-gradient(135deg, #fde68a, #fb923c);
border-radius: 14px;
font: 700 18px sans-serif;
}Line-by-line explanation
- Lines 2 to 4A horizontal row that scrolls sideways.
- Line 5Snap on the x axis, always.
- Line 6Snapped cards line up with the 12px page padding.
- Lines 12 to 13Each card is 75% wide so the next one peeks in, and its start edge snaps.
Smooth jumps below a sticky header
Click the links. The page scrolls smoothly and headings stop below the header.
<div class="page">
<header class="bar"><a href="#hours">Hours</a> <a href="#menu">Menu</a> <a href="#find">Find us</a></header>
<h2 id="hours">Hours</h2><p class="filler">Open every day from 8am to 6pm.</p>
<h2 id="menu">Menu</h2><p class="filler">Breakfast, lunch and cakes.</p>
<h2 id="find">Find us</h2><p class="filler">12 Harbour Street.</p>
</div>.page {
height: 260px;
overflow-y: auto;
scroll-behavior: smooth;
font-family: sans-serif;
border: 1px solid #e5e7eb;
}
.bar { position: sticky; top: 0; background: #111827; padding: 12px; }
.bar a { color: white; margin-right: 12px; }
h2 { scroll-margin-top: 56px; margin: 0; padding: 8px 12px; }
.filler { min-height: 200px; margin: 0; padding: 0 12px; }Line-by-line explanation
- Line 4Jumps inside this box animate smoothly.
- Line 9The header stays at the top while scrolling.
- Line 12Each heading stops 56px from the top, just below the header.
Responsive columns for links and cards
Narrow the panel and the columns drop from three to one.
<ul class="links">
<li>Getting started</li><li>Installation</li><li>Configuration</li><li>Themes</li>
<li>Plugins</li><li>Deployment</li><li>Testing</li><li>Performance</li><li>Security</li>
</ul>
<div class="masonry">
<div class="tile" style="height: 80px">A</div><div class="tile" style="height: 130px">B</div>
<div class="tile" style="height: 100px">C</div><div class="tile" style="height: 70px">D</div>
</div>.links {
columns: 3 150px;
column-gap: 24px;
column-rule: 1px solid #e5e7eb;
font-family: sans-serif;
}
.masonry { columns: 2 140px; column-gap: 10px; margin-top: 16px; }
.tile {
break-inside: avoid;
margin-bottom: 10px;
display: grid; place-items: center;
background: #e0e7ff; border-radius: 10px; font: 700 18px sans-serif;
}Line-by-line explanation
- Line 2Up to three columns, each at least 150px.
- Line 4A thin line between columns.
- Line 8Tiles of different heights stack into columns, a simple masonry look.
- Line 10No tile is split across two columns.
Common CSS Scroll Snap Mistakes
Snapping on the children instead of the container
.slide { scroll-snap-type: x mandatory; } /* no effect */.track { scroll-snap-type: x mandatory; }
.slide { scroll-snap-align: start; }Headings hidden under a sticky header
header { position: sticky; top: 0; height: 64px; } /* #section links land under it */header { position: sticky; top: 0; height: 64px; }
:target { scroll-margin-top: 72px; }Long articles in columns
article { columns: 3; } /* readers scroll down and back up for every column */article { max-width: 65ch; }
.related-links { columns: 3 180px; }CSS Scroll Snap in Real Projects
Where you'll see these
App stores, streaming services and online shops use snap carousels for rows of apps, shows and products. Documentation sites, including this one, use smooth scrolling with scroll-margin-top so table-of-contents links land neatly below the sticky header. Footers and site maps use multi-column lists, and image sites like Pinterest popularised the masonry look that columns approximates.
CSS Scroll Snap Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Make the container snap horizontally on every scroll.
.track {
overflow-x: auto;
scroll-snap-type: x ;
}
mandatory always snaps to an item.Which property goes on each slide?
What does scroll-margin-top: 80px do on a heading?
What does columns: 3 200px mean?
The track should snap on the x axis, each slide should snap at its start, and links inside the page should scroll smoothly. Fix it.
<div class="tr"><div class="sl">1</div><div class="sl">2</div></div>.tr { display: flex; overflow-x: auto; }
.sl { flex: 0 0 80%; scroll-snap-type: x mandatory; }
html { scroll-behavior: auto; }- Container snaps on x
- Slides snap at the start
- Smooth scrolling
CSS Scroll Snap Quiz
5 questions to check what you learned.
What does scroll-behavior: smooth change?
- Jumps to anchor links animate instead of happening instantly
- Scroll speed with the mouse wheel
- It hides scrollbars
- It makes pages load faster
When is proximity better than mandatory?
- Never
- For small carousels
- When using columns
- When snapped items can be taller than the screen, such as long sections
Why make each slide about 80% wide?
- It's required for snapping
- It loads faster
- The next slide peeks in, showing users they can swipe
- It hides the scrollbar
Which property stops a card being split between two columns?
- column-span: none
- break-inside: avoid
- overflow: hidden
- display: block
How do you make a carousel usable with the keyboard?
- It's not possible without JavaScript
- Give the scroll container tabindex="0" and a label so it can be focused and scrolled with arrow keys
- Use scroll-snap-stop
- Add hover styles
CSS Scroll Snap Challenge
A gallery page
- Make
.gallerya flex row that scrolls sideways (overflow-x: auto) withscroll-snap-type: x mandatoryandgap: 10px - Give
.itemflex: 0 0 70%andscroll-snap-align: center - Give
.notescolumns: 2 160pxandcolumn-gap: 20px - Give
h2ascroll-margin-topof60px
<h2 id="photos">Photos</h2>
<div class="gallery" tabindex="0" aria-label="Photos">
<div class="item">1</div><div class="item">2</div><div class="item">3</div>
</div>
<ul class="notes"><li>Taken in May</li><li>Shot on film</li><li>Prints available</li><li>Ask about licensing</li></ul>.item { height: 100px; background: #dbeafe; }
- Gallery scrolls and snaps
- Items are 70% wide and centre-snap
- Notes use columns
- Heading scroll margin