Custom Scrollbars
Custom scrollbar styling lets you restyle a browser's scroll track and thumb to match your theme using the standard scrollbar-color and scrollbar-width properties, plus the WebKit ::-webkit-scrollbar pseudo-elements for finer control.
Part of the free HTML & CSS course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
By the end you'll style any scrollbar to match your theme — in every browser — without hurting usability.
What You'll Learn
💡 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.
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 .
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.
🎯 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.
🎯 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.
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-type for 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.
Common Errors & Fixes
- "My scrollbar styles work in Chrome but not Firefox." You only wrote ::-webkit-scrollbar rules, which Firefox ignores. Fix: add scrollbar-width and scrollbar-color to the same selector.
- "No scrollbar appears at all." The element isn't actually overflowing. Fix: give it a fixed height (or max-height ) and overflow-y: auto — a scrollbar only shows when content is taller than the box.
- "My horizontal bar is invisible." You set width on ::-webkit-scrollbar . A horizontal bar's thickness is its height . Fix: use ::-webkit-scrollbar { height: 8px; } and overflow-x: auto on 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; thin is fine, hair-thin is not.
- "People keep missing that the box scrolls." You hid the bar with scrollbar-width: none on 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
Whole bar (width/height)
{ width: 8px; }
Draggable handle
{ background: #1976D2; }
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
🎉 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 ; add scroll-snap-type for carousels
- ✅ scroll-behavior: smooth animates jumps; overscroll-behavior: contain stops chaining
- ✅ Keep bars visible and at least ~6px wide — never invisible or too thin to grab
- ✅ Next lesson: Accessible Modals & Dialogs
Practice quiz
In the standard scrollbar-color property, which order are the two colors given?
- track then thumb
- background then border
- thumb then track
- Both must be identical
Answer: thumb then track. scrollbar-color takes the thumb color first, then the track color.
Which values are valid for scrollbar-width?
- auto | thin | none
- small | medium | large
- 1px | 2px | 3px
- show | hide
Answer: auto | thin | none. scrollbar-width accepts the keywords auto, thin, or none.
Why won't ::-webkit-scrollbar rules style the scrollbar in Firefox?
- Firefox needs a plugin
- Firefox disables all scrollbars
- You must use !important
- The pseudo-elements are WebKit-only; Firefox uses the standard properties
Answer: The pseudo-elements are WebKit-only; Firefox uses the standard properties. Firefox ignores the WebKit pseudo-elements and styles scrollbars via scrollbar-width/scrollbar-color.
Which pseudo-element styles the draggable handle of a WebKit scrollbar?
- ::-webkit-scrollbar-track
- ::-webkit-scrollbar-thumb
- ::-webkit-scrollbar
- ::-webkit-scrollbar-handle
Answer: ::-webkit-scrollbar-thumb. ::-webkit-scrollbar-thumb styles the handle you drag; -track is the groove behind it.
For a horizontal scrollbar, which property sets its thickness on ::-webkit-scrollbar?
- height
- width
- thickness
- size
Answer: height. A horizontal bar's thickness is its height; vertical bars use width.
What does scroll-behavior: smooth do?
- Changes the scrollbar color
- Hides the scrollbar
- Animates programmatic and anchor-link scrolling instead of jumping
- Snaps elements into place
Answer: Animates programmatic and anchor-link scrolling instead of jumping. scroll-behavior: smooth animates jumps (anchor links, scrollIntoView); it doesn't change appearance.
What does overscroll-behavior: contain prevent?
- The scrollbar from appearing
- Scroll chaining to the page behind a panel
- Smooth scrolling
- Horizontal scrolling
Answer: Scroll chaining to the page behind a panel. overscroll-behavior: contain stops scroll chaining so the page behind a panel doesn't take over.
Which property makes a scroll container snap each child into place?
- scroll-behavior
- overscroll-behavior
- scrollbar-width
- scroll-snap-type
Answer: scroll-snap-type. scroll-snap-type (e.g. x mandatory) makes children snap into place; scroll-snap-align positions each child.
What is required for any scrollbar to appear in the first place?
- A color-scheme declaration
- Content that overflows a fixed-size box (height + overflow)
- A WebKit browser
- The scrollbar-width property
Answer: Content that overflows a fixed-size box (height + overflow). A scrollbar only shows when content overflows a box with a fixed size and overflow: auto/scroll.
Why is hiding the scrollbar on ordinary vertical content an accessibility problem?
- It slows the page down
- It breaks in Chrome
- Users lose the visual cue that there is more content to read
- It disables keyboard scrolling
Answer: Users lose the visual cue that there is more content to read. Hiding the bar (scrollbar-width: none) removes the cue that more content exists below.
Continue this course
- Previous: Progressive Enhancement
- Next: Accessible Modals