Lesson 46 • Advanced Track
Custom Scrollbars & UI Chrome Styling
By the end you'll style any scrollbar to match your theme — in every browser — without hurting usability.
What You'll Learn
- Style scrollbars the standard way with scrollbar-width and scrollbar-color
- Use the WebKit ::-webkit-scrollbar / -thumb / -track pseudo-elements for fine control
- Animate scrolling with scroll-behavior: smooth and snap cards with scroll-snap
- Trap scrolling inside a panel with overscroll-behavior: contain
- Write one cross-browser snippet that styles scrollbars everywhere
- Keep scrollbars usable and accessible — never invisible or too thin to click
height and overflow work together.💡 Real-World Analogy
Think of a scrollbar like the volume slider on a hi-fi. The track is the groove it slides in, and the thumb is the knob you grab. Default scrollbars are the plain plastic knob the factory fitted; a custom scrollbar is the same mechanism with a knob that matches your gear. You're repainting the knob — not changing how it works — so keep it big enough to grab and easy to see.
The Two Scrollbar APIs
A scrollbar has two parts you can style: the thumb (the bit you drag) and the track (the groove behind it). There are two ways to style them, and you need both.
The standard properties. scrollbar-width takes auto, thin, or none, and scrollbar-color takes two colours — thumb first, then track. These work in Firefox and modern Chrome (121+). They're simple but you only get colour and a coarse width.
The WebKit pseudo-elements. ::-webkit-scrollbar targets the whole bar, ::-webkit-scrollbar-thumb the handle, and ::-webkit-scrollbar-track the groove. They work in Chrome, Safari and Edge and give you exact pixel widths, border-radius, borders, gradients and :hover states — but Firefox ignores them completely.
The takeaway: write both. The standard properties cover Firefox; the WebKit pseudo-elements add the polish for everyone else.
📊 WebKit Scrollbar Pseudo-Elements
| Pseudo-Element | What It Styles | Common Properties |
|---|---|---|
| ::-webkit-scrollbar | The entire scrollbar | width, height |
| ::-webkit-scrollbar-thumb | The draggable handle | background, border-radius, border |
| ::-webkit-scrollbar-track | The groove behind the thumb | background, border-radius |
| ::-webkit-scrollbar-thumb:hover | The handle on hover | background (darker shade) |
1. Both APIs Together (Worked Example)
Read every comment, then run it. The same box gets a styled scrollbar in both Firefox and Chrome because it declares the standard properties and the WebKit pseudo-elements together.
One scrollbar, two APIs
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui, sans-serif; padding: 24px; color: #333; }
h1 { color: #1565C0; }
/* A box that is taller than its content area, so it must scroll. */
.scroll-box {
height: 180px; /* fixed height ... */
overflow-y: auto; /* ... + overflow:auto = a vertical scrollbar */
border: 1px solid #ddd;
border-radius: 8px;
padding: 16px;
}
/* --- API 1: the STANDARD properties (Firefox + modern Chrome) ---
...2. Smooth Scrolling & Scroll-Snap
scroll-behavior: smooth animates jumps (handy for anchor links). scroll-snap-type makes a scroll container snap each child into place — the classic carousel feel. Note the horizontal bar uses height, not width.
Smooth scroll + snap carousel
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui, sans-serif; padding: 24px; color: #333; }
h1 { color: #1565C0; }
/* scroll-behavior: smooth animates jumps (anchor links, scrollTo). */
html { scroll-behavior: smooth; }
.row {
display: flex;
gap: 16px;
overflow-x: auto; /* horizontal scroll -> use HEIGHT on the bar */
padding-bottom: 12px;
scrollbar-width: thin;
scrollbar-color: #FF9800 #FFF3E0;
/* scroll-snap-type makes the row
...3. Containing the Scroll with overscroll-behavior
When you scroll to the end of a panel, the page behind it normally takes over — that's "scroll chaining". overscroll-behavior: contain stops it, which is exactly what you want for chat windows and modals.
overscroll-behavior: contain
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui, sans-serif; padding: 24px; color: #333; height: 1200px; }
h1 { color: #1565C0; }
.panel {
height: 160px;
overflow-y: auto;
border: 1px solid #ddd; border-radius: 8px; padding: 16px;
scrollbar-width: thin;
scrollbar-color: #4CAF50 #E8F5E9;
/* overscroll-behavior: contain stops the "scroll chaining" effect — when
you reach the bottom of this panel, the PAGE behind it does NOT start
sc
...🎯 Your Turn #1 — Standard Properties
Fill in the blanks so the box gets a thin scrollbar with a purple thumb on a light track. This is the Firefox / standards approach — only two lines.
Fill in scrollbar-width & scrollbar-color
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui, sans-serif; padding: 24px; color: #333; }
.box {
height: 160px;
overflow-y: auto; /* makes it scrollable */
border: 1px solid #ddd; border-radius: 8px; padding: 16px;
/* 🎯 YOUR TURN — fill in the blanks marked with ___ */
/* 1) Make the scrollbar thin (auto | thin | none) */
scrollbar-width: ___; /* 👉 replace ___ with the keyword for slim */
/* 2) Set the thumb to purple (
...🎯 Your Turn #2 — WebKit Pseudo-Elements
Now do the same job the WebKit way. Decide whether each blank is width/height, thumb/track, and which :state gives hover feedback.
Finish the WebKit scrollbar
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui, sans-serif; padding: 24px; color: #333; }
.box { height: 160px; overflow-y: auto; border: 1px solid #ddd;
border-radius: 8px; padding: 16px; }
/* 🎯 YOUR TURN — finish the WebKit scrollbar (Chrome/Safari/Edge) */
/* 1) Make the whole scrollbar 12px wide */
.box::-webkit-scrollbar { ___: 12px; } /* 👉 width or height? */
/* 2) Give the draggable handle a teal background + rounded corners */
.bo
...When to Use Custom Scrollbars
- Dark-themed apps: default grey bars look jarring on dark backgrounds — match the thumb to your theme.
- Chat & messaging: a thin 4–6px bar feels more app-like; pair it with
overscroll-behavior: contain. - Code editors: a subtle dark-on-dark bar keeps the focus on the code.
- Carousels: style (or hide on touch) the horizontal bar and add
scroll-snap-typefor polish.
🏆 Mini-Challenge — Dark-Mode Chat Scrollbar
Support is faded now — you get only a comment outline. Build a subtle dark-mode scrollbar on .chat using both APIs, plus overscroll-behavior: contain. Check your work against the expected result in the comment.
Mini-Challenge: style .chat
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui, sans-serif; padding: 24px; color: #ccc;
background: #1a1a2e; }
h2 { color: #90CAF9; }
/* 🎯 MINI-CHALLENGE: a dark-mode chat panel scrollbar
1. Make ".chat" 200px tall and scrollable (height + overflow-y).
2. Add overscroll-behavior: contain so the page doesn't scroll behind it.
3. STANDARD API: scrollbar-width thin; scrollbar-color = #555 thumb on #1a1a2e track.
4. WEBKIT API: 8px wide bar;
...Common Errors & Fixes
- "My scrollbar styles work in Chrome but not Firefox." You only wrote
::-webkit-scrollbarrules, which Firefox ignores. Fix: addscrollbar-widthandscrollbar-colorto the same selector. - "No scrollbar appears at all." The element isn't actually overflowing. Fix: give it a fixed
height(ormax-height) andoverflow-y: auto— a scrollbar only shows when content is taller than the box. - "My horizontal bar is invisible." You set
widthon::-webkit-scrollbar. A horizontal bar's thickness is itsheight. Fix: use::-webkit-scrollbar { height: 8px; }andoverflow-x: autoon the container. - "Users say they can't grab the scrollbar." You made it too thin (1–3px). Fix: keep desktop bars at least 6–8px wide so they're clickable;
thinis fine, hair-thin is not. - "People keep missing that the box scrolls." You hid the bar with
scrollbar-width: noneon vertical content. Fix: only hide scrollbars where swipe is obvious (touch carousels); otherwise keep a visible cue.
📋 Quick Reference
| Property / Selector | Does | Example |
|---|---|---|
| scrollbar-width | Standard bar thickness | thin |
| scrollbar-color | Thumb then track colour | #1976D2 #f0f0f0 |
| ::-webkit-scrollbar | Whole bar (width/height) | { width: 8px; } |
| ::-webkit-scrollbar-thumb | Draggable handle | { background: #1976D2; } |
| ::-webkit-scrollbar-track | Groove behind thumb | { background: #f0f0f0; } |
| scroll-behavior | Animate scroll jumps | smooth |
| scroll-snap-type | Snap children into place | x mandatory |
| overscroll-behavior | Stop scroll chaining | contain |
💡 Pro tip: always pair the standard properties with the WebKit pseudo-elements so every browser gets a styled bar.
Frequently Asked Questions
Why isn't my custom scrollbar showing in Firefox?
Firefox ignores the ::-webkit-scrollbar pseudo-elements — they are WebKit-only. Firefox styles scrollbars with the standard scrollbar-width and scrollbar-color properties instead. Always write both APIs: the standard properties for Firefox and modern Chrome, and the ::-webkit-scrollbar pseudo-elements for Chrome, Safari and Edge.
Should I style horizontal scrollbars differently?
The pseudo-elements are the same, but you set height instead of width on ::-webkit-scrollbar (height controls the thickness of a horizontal bar). The container also needs overflow-x: auto instead of overflow-y. Everything else — thumb, track, hover — works identically.
Is it OK to completely hide the scrollbar?
Only when scrolling is obvious another way — for example a horizontal card carousel on a touch device where users swipe. Hiding the scrollbar on ordinary vertical content (scrollbar-width: none) is an accessibility problem because people lose the visual cue that there is more to read. When in doubt, keep a visible bar.
What does scroll-behavior: smooth do?
It animates programmatic and anchor-link scrolling instead of jumping instantly. Set it on the html element (or a scroll container) and clicking an in-page #anchor link, or calling element.scrollIntoView(), glides smoothly. It has no effect on the scrollbar's appearance — it only changes how the scroll motion looks.
When should I use overscroll-behavior?
Use overscroll-behavior: contain on scrollable panels like chat windows, dropdowns and modals. It stops 'scroll chaining' — reaching the end of the inner panel no longer scrolls the page behind it. This keeps the user's focus inside the panel and prevents the surprising jump where the whole page starts moving.
🎉 Lesson Complete
- ✅ Standard API:
scrollbar-width+scrollbar-color(thumb then track) - ✅ WebKit API:
::-webkit-scrollbar/-thumb/-track+:hover - ✅ Always write both APIs for full cross-browser coverage
- ✅ Horizontal bars use
height; addscroll-snap-typefor carousels - ✅
scroll-behavior: smoothanimates jumps;overscroll-behavior: containstops chaining - ✅ Keep bars visible and at least ~6px wide — never invisible or too thin to grab
- ✅ Next lesson: Accessible Modals & Dialogs
Sign up for free to track which lessons you've completed and get learning reminders.