CSS Scroll Behavior
CSS controls how, where, and how fast a page scrolls. From smooth page navigation to snapping carousels to hiding scrollbars, scroll-related CSS properties create a polished, intentional reading experience without any JavaScript.
scroll-behavior — Smooth vs Instant
By default, clicking an anchor link (<a href="#section">) jumps instantly to the target. The scroll-behavior property makes this scroll smooth and animated instead.
html {
scroll-behavior: smooth;
}
DIAGRAM — scroll-behavior:
scroll-behavior: auto (default)
Click "About" link → page JUMPS instantly to the About section
scroll-behavior: smooth
Click "About" link → page GLIDES down to the About section
Two values are available:
auto— instant jump (default)smooth— animated glide
Accessibility Note
Some users experience motion sickness from scrolling animations. Respect the operating system's reduced-motion preference.
html {
scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
}
scroll-margin and scroll-padding — Offset the Scroll Target
When you have a sticky header, smooth-scrolling to an anchor lands the heading behind the header. scroll-padding and scroll-margin fix this.
scroll-padding-top (on the scroll container)
html {
scroll-behavior: smooth;
scroll-padding-top: 80px; /* matches your sticky header height */
}
DIAGRAM — Without vs with scroll-padding-top:
Without:
┌────────────────────┐
│Sticky Header(80px) │ ← header
│████████████████████│
│ ## Section Title │ ← hidden behind header!
│ content... │
With scroll-padding-top: 80px:
┌────────────────────┐
│Sticky Header(80px) │ ← header
│────────────────────│
│ ## Section Title │ ← visible below header
│ content... │
scroll-margin-top (on the target element)
section {
scroll-margin-top: 80px;
/* Add gap above this element when it's scrolled to */
}
Both approaches achieve the same visual result. Use scroll-padding-top on html as a global solution, or scroll-margin-top on individual elements when you need per-element control.
overflow — Controlling Scrollable Areas
The overflow property controls what happens when content is larger than its container. It also determines whether a scrollbar appears.
DIAGRAM — overflow values:
Content bigger than box:
overflow: visible → content spills outside the box (default)
overflow: hidden → content is clipped, no scrollbar
overflow: scroll → always shows scrollbar (even if not needed)
overflow: auto → shows scrollbar only when content overflows
/* Scrollable code block */
pre {
overflow-x: auto; /* horizontal scroll only */
white-space: nowrap;
}
/* Scrollable sidebar */
.sidebar {
height: 100vh;
overflow-y: auto; /* vertical scroll only */
}
/* No scrollbar, but content is clipped */
.card {
overflow: hidden;
border-radius: 8px;
}
CSS Scroll Snap — Controlled Snapping
Scroll snap makes a scrollable container lock onto defined positions as the user scrolls — like a slideshow or a mobile page-by-page view.
On the Container (the scroll parent)
.carousel {
display: flex;
overflow-x: scroll;
scroll-snap-type: x mandatory;
/* x = horizontal, mandatory = always snap to a position */
}
On Each Snap Target (the children)
.slide {
flex: 0 0 100%; /* each slide is full width */
scroll-snap-align: start;
}
DIAGRAM — scroll-snap-type: x mandatory:
Scrolling freely: →→→
┌──┬──┬──┬──┬──┐
│1 │2 │3 │4 │5 │ ← slides in a row
After releasing: SNAP
┌──────┐
│ 3 │ ← snaps to the nearest full slide
└──────┘
No half-visible slides — every stop is a clean frame.
Snap Alignment Options
scroll-snap-align: start— snaps the start (left/top) edge of the item to the container edgescroll-snap-align: center— snaps the item's center to the container's centerscroll-snap-align: end— snaps the end (right/bottom) edge
Proximity vs Mandatory
/* mandatory — always snaps, even on small scroll */
scroll-snap-type: x mandatory;
/* proximity — only snaps if close enough to a snap point */
scroll-snap-type: x proximity;
Vertical Page Sections
.page-wrapper {
height: 100vh;
overflow-y: scroll;
scroll-snap-type: y mandatory;
}
section {
height: 100vh;
scroll-snap-align: start;
}
DIAGRAM — Full-screen vertical snap sections:
┌─────────────────┐ ─── scroll starts here
│ Section 1 │ ↑ snaps here
│ (100vh tall) │
├─────────────────┤ ↑ snaps here
│ Section 2 │
│ (100vh tall) │
├─────────────────┤ ↑ snaps here
│ Section 3 │
└─────────────────┘
Hiding the Scrollbar While Keeping Scroll
Useful for carousels and horizontal scroll areas where the scrollbar looks out of place.
.scroll-container {
overflow-x: auto;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE/Edge */
}
/* Chrome, Safari */
.scroll-container::-webkit-scrollbar {
display: none;
}
Custom Scrollbar Styling
/* Works in Chrome, Edge, Safari */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
}
::-webkit-scrollbar-thumb {
background: #aaa;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #888;
}
Quick Reference
| Property | What It Does |
|---|---|
| scroll-behavior: smooth | Animates anchor-link scrolling |
| scroll-padding-top | Adds top offset for sticky headers |
| scroll-margin-top | Per-element scroll target offset |
| overflow-y: auto | Vertical scrollbar when needed |
| overflow-x: auto | Horizontal scrollbar when needed |
| scroll-snap-type | Enable snapping on a container |
| scroll-snap-align | Define snap position on children |
| scrollbar-width: none | Hide scrollbar (Firefox) |
Scroll behavior is one of the most noticeable parts of a user's experience. Smooth anchors, properly offset headings, and snapping carousels feel polished and intentional — and all of it is achievable with a few CSS properties and no JavaScript.
