Modern Scrollbar Styling: Beyond the Browser Defaults
A decade of WebKit-only scrollbar CSS did absolutely nothing in Firefox, which shipped its own answer years earlier. Chrome and Safari finally caught up to the standard properties in 2024.
Paul Radford — Full-Stack Developer & Editor. Paul is a full-stack developer and editor of template.tips. Articles here are AI-drafted and reviewed by Paul, who knows the code well enough to catch what's wrong and cut the hype.

You style scrollbars today with the standard scrollbar-color and scrollbar-width properties for cross-browser control, falling back to the older ::-webkit-scrollbar pseudo-elements only when you need per-part styling (thumb, track, corner) that the standard properties do not expose. Both approaches now coexist in every major engine.
The short history that explains the mess
For over a decade, styling a scrollbar meant writing WebKit-only CSS: ::-webkit-scrollbar, ::-webkit-scrollbar-thumb, ::-webkit-scrollbar-track. It worked in Chrome and Safari, did nothing in Firefox, and Firefox users got the platform default no matter what you wrote. Firefox shipped its own answer, scrollbar-color and scrollbar-width, back in Firefox 64. For years that left front-end teams writing two code paths and accepting that one browser family would always look different.
That gap closed relatively recently. Chrome added support for scrollbar-color and scrollbar-width in Chrome 121 (January 2024), and Safari followed with Safari 18.2 in December 2024. As of 2026, you can reasonably treat the standard properties as your primary tool and treat the WebKit pseudo-elements as a legacy fallback or a way to get finer visual control than the standard properties allow. The CSS Scrollbars Module Level 1 spec is what standardized this, and it is worth reading once so you understand why the standard properties are intentionally limited.
What CSS properties actually control scrollbar appearance now?
Two properties do almost everything the spec allows: scrollbar-width (auto, thin, or none) and scrollbar-color (a thumb color and a track color, in that order). They apply to any scrollable element, not just the document.
1/* Standard scrollbar styling: works in current Chrome, Firefox, Safari, Edge */2.scroll-panel {3 overflow-y: auto;4 max-height: 400px;56 /* thin removes the bulky default width, none hides it entirely */7 scrollbar-width: thin;89 /* first color = thumb, second color = track */10 scrollbar-color: #8a8a8a #1e1e1e;11}1213/* Respect user color scheme instead of hardcoding values */14@media (prefers-color-scheme: dark) {15 .scroll-panel {16 scrollbar-color: #6b7280 #111827;17 }18}This is the whole surface area. There is no scrollbar-radius, no way to add padding around the thumb, no way to style the corner where horizontal and vertical scrollbars meet. If your design calls for anything beyond color and thickness, the standard properties will not get you there, and you either accept the platform look or reach for the non-standard pseudo-elements below.
Do you still need the WebKit pseudo-elements?
Sometimes, yes. ::-webkit-scrollbar and its siblings remain non-standard (they never went through a W3C or WHATWG process and MDN documents them as such), but they are supported in every Chromium browser and in Safari, which covers the majority of desktop and mobile traffic. Firefox ignores them silently, which is exactly the behavior you want: it just falls through to whatever scrollbar-color you also declared.
1/* Fallback for finer control in Chromium and Safari */2.scroll-panel: :-webkit-scrollbar {3 width: 10px;4}56.scroll-panel: :-webkit-scrollbar-track {7 background: #1e1e1e;8 border-radius: 8px;9}1011.scroll-panel: :-webkit-scrollbar-thumb {12 background: #8a8a8a;13 border-radius: 8px;14 border: 2px solid #1e1e1e; /* creates a padding illusion around the thumb */15}1617.scroll-panel: :-webkit-scrollbar-thumb:hover {18 background: #a0a0a0;19}2021.scroll-panel: :-webkit-scrollbar-corner {22 background: transparent;23}Layer both blocks in the same stylesheet and you get near-identical results across engines without a fork in your build. The only real cost is maintaining two sets of rules that have to stay visually in sync, which is a minor tax compared to the two-codebase situation from a few years ago.
How do overlay scrollbars change your layout math?
macOS Safari and Chrome on macOS use overlay scrollbars by default: the scrollbar draws on top of content and takes up no layout space until the user hovers or scrolls. Windows and most Linux desktop environments use classic scrollbars that always reserve a track width, which shifts your content by however many pixels wide that scrollbar is.
This difference causes a specific, recurring bug: a fixed-width sidebar or a centered layout that looks perfectly aligned on a MacBook shifts by 15 to 17 pixels on Windows the moment content becomes tall enough to scroll. The fix is scrollbar-gutter, a property built exactly for this.
1/* Reserve space for the scrollbar before it appears, avoiding layout shift */2html {3 scrollbar-gutter: stable;4}56/* If you have a two-column layout, reserve gutter on both edges */7.two-column-layout {8 scrollbar-gutter: stable both-edges;9}Check MDN's scrollbar-gutter reference before using both-edges. It is meant for layouts where scrollable regions live on both sides of the viewport, and using it unnecessarily reserves dead space that makes single-column layouts look unbalanced.
When should custom scrollbar styling actually be avoided?
Three situations, in my experience, argue against touching scrollbars at all:
First, forced-colors mode (Windows high-contrast mode) overrides most author-supplied colors for accessibility reasons, including scrollbar colors in some browser implementations. If your product has users who rely on high-contrast settings, test with it enabled rather than assuming your scrollbar-color values will always render. Design your thumb and track colors with enough contrast on their own that if the override kicks in, nothing breaks visually.
Second, touch devices mostly do not render persistent scrollbars at all, they show a transient indicator during a scroll gesture and hide it otherwise. Spending design time perfecting a custom thumb color for a scrollbar that 60 percent of your traffic will never see as a static element is a poor use of a sprint.
Third, low-contrast custom scrollbars are a genuine usability regression. A common mistake is matching the thumb color too closely to the track for a "subtle" look, which makes the scrollbar functionally invisible against certain backgrounds and defeats its purpose as a navigation and progress indicator. If you cannot tell, at a glance, roughly how far down a long page you are, the styling has failed regardless of how good it looks in a design mockup.
A practical pattern for content-heavy panels
Here is the pattern I reach for on dashboards or chat panels that need a scrollbar that matches a dark UI without fighting the browser:
1/* Reusable utility class, combine standard + webkit rules */2.themed-scroll {3 scrollbar-width: thin;4 scrollbar-color: var(--scroll-thumb) var(--scroll-track);5}67.themed-scroll: :-webkit-scrollbar {8 width: 8px;9 height: 8px; /* covers horizontal scroll containers too */10}1112.themed-scroll: :-webkit-scrollbar-track {13 background: var(--scroll-track);14}1516.themed-scroll: :-webkit-scrollbar-thumb {17 background: var(--scroll-thumb);18 border-radius: 4px;19}Define --scroll-thumb and --scroll-track at the theme level alongside your other design tokens, rather than hardcoding hex values inside the utility class. That keeps the scrollbar in sync automatically when someone flips a dark-mode toggle, instead of becoming a stray rule someone forgets to update six months later.
Testing it properly before you ship
Do not trust a single browser preview here. Open the panel in actual Firefox and actual Safari, not just Chrome DevTools' device emulation, because emulation does not simulate scrollbar rendering differences. Resize the window narrow enough to force a horizontal scrollbar and confirm the corner and horizontal thumb look intentional rather than accidental. Toggle your OS's high-contrast or forced-colors setting and confirm the panel is still usable. Finally, check the container's behavior when content is short enough that no scrollbar appears at all: with overflow-y: auto, a properly styled scrollbar should simply not be there, and layout should not jump when content grows past the threshold.
If you get all four checks passing, you have a scrollbar implementation that will hold up outside your own machine, which is a lower bar to clear than it sounds and one that a surprising number of shipped interfaces fail on the first attempt.
Related Articles

Creating Glass Effects with backdrop-filter: From Blur to Frosted Glass
filter blurs an element's own contents, including any text sitting on top of it. backdrop-filter blurs only what's behind the element, the exact distinction that makes glassmorphism work at all.

clip-path and mask: Advanced Shape Techniques for the Modern Web
Both properties hide part of an element without any extra markup, but clip-path is strictly binary while mask is a full grayscale operation. Here's which one fits which visual effect.

light-dark(): The Simplest Way to Handle Color Mode Switching
Forty color tokens duplicated inside a prefers-color-scheme media query block is forty places to miss an override somewhere. light-dark() collapses that pattern into one line per property.