Scrolling
The browser scrolls by itself, but CSS can tune it: make a jump to an anchor smooth, stop a carousel exactly on a slide, keep scrolling from "spilling" onto the page. In the examples it is not the window that scrolls but a block with overflow: auto from the overflow lesson — the properties work the same way.
Smooth scrolling
A link like <a href="#prices"> from the links lesson jumps to the target instantly. scroll-behavior: smooth makes this jump smooth. In the example, buttons replace the links: they do the same jump from JavaScript, and container: 'nearest' asks to scroll only the nearest block, not the whole lesson page:
<style>
.box { height: 140px; overflow: auto; scroll-behavior: smooth; border: 1px solid silver; }
.box p { height: 120px; margin: 0; padding: 8px; }
</style>
<button onclick="document.querySelector('#end').scrollIntoView({ container: 'nearest' })">Down</button>
<button onclick="document.querySelector('#start').scrollIntoView({ container: 'nearest' })">Up</button>
<div class="box">
<p id="start">Start</p>
<p>Middle</p>
<p id="end">End</p>
</div>For the whole page the rule is written like this: html { scroll-behavior: smooth; }.
An anchor under a sticky header
If the header sticks to the top (position: sticky or fixed), a jump to an anchor hides the section heading under it. scroll-margin-top leaves space above the target:
<style>
.page { height: 160px; overflow: auto; border: 1px solid silver; }
.page header { position: sticky; top: 0; padding: 8px; background: steelblue; color: white; }
.page section { height: 150px; padding: 0 8px; scroll-margin-top: 44px; }
</style>
<button onclick="document.querySelector('#delivery').scrollIntoView({ container: 'nearest' })">To delivery</button>
<div class="page">
<header>Shop</header>
<section><h3>Catalog</h3></section>
<section id="delivery"><h3>Delivery</h3></section>
<section><h3>Contacts</h3></section>
</div>The margin equals the header height plus a little extra. Remove scroll-margin-top, and the "Delivery" heading ends up under the header.
A carousel: scroll-snap
Scroll snapping stops scrolling exactly on an element instead of anywhere. The container gets scroll-snap-type, the items get a snap point with scroll-snap-align. Scroll the strip sideways:
<style>
.carousel { display: flex; gap: 8px; overflow-x: auto; scroll-snap-type: x mandatory; }
.slide { flex: 0 0 80%; height: 100px; display: grid; place-items: center; scroll-snap-align: center; color: white; font-size: 24px; }
</style>
<div class="carousel">
<div class="slide" style="background: tomato;">1</div>
<div class="slide" style="background: seagreen;">2</div>
<div class="slide" style="background: steelblue;">3</div>
</div>Wherever you let go of the strip, it slides on to the nearest slide and centres it.
Snap values
| Property | Value | What it does |
|---|---|---|
scroll-snap-type | x or y | the axis snapping works on |
mandatory | always stop on an element | |
proximity | only if scrolling stopped close to one | |
scroll-snap-align | start, center, end | which edge of the element to align |
overscroll-behavior
When a nested block is scrolled to the end, further wheel movements start scrolling the page. This is called scroll chaining. overscroll-behavior: contain stops it at the edge of the block:
<style>
.lists { display: flex; gap: 8px; }
.list { flex: 1; height: 110px; overflow: auto; border: 1px solid silver; }
.list p { margin: 0; padding: 6px 8px; }
.contain { overscroll-behavior: contain; }
</style>
<div class="lists">
<div class="list"><p>A regular list</p><p>Anna</p><p>Ben</p><p>Clara</p><p>David</p><p>Emma</p></div>
<div class="list contain"><p>contain</p><p>Anna</p><p>Ben</p><p>Clara</p><p>David</p><p>Emma</p></div>
</div>Scroll each list to the end with the wheel and keep going: in the left one the lesson page starts scrolling, in the right one it does not. This is used for dropdown menus, chats and modal windows.
Common mistake
scroll-snap-type is given to the items instead of the container — and snapping does not work. The snap type belongs to the element that scrolls, while the items get only scroll-snap-align:
<style>
.carousel { display: flex; gap: 8px; overflow-x: auto; }
.slide { flex: 0 0 80%; height: 80px; background: gold; scroll-snap-type: x mandatory; scroll-snap-align: start; }
</style>
<div class="carousel">
<div class="slide">The strip</div>
<div class="slide">stops</div>
<div class="slide">anywhere</div>
</div>