Type something to search...
Styling Scrollbars with CSS: A Cross-Browser Guide

Styling Scrollbars with CSS: A Cross-Browser Guide

Scrollbars are one of the few pieces of browser chrome that sit inside your design. On a dark-themed dashboard, a bright gray system scrollbar running down a sidebar can look like a mistake. On a code block or a horizontally scrolling card row, a chunky scrollbar can eat into content space. So it's no surprise that "how do I style the scrollbar?" is one of the most searched CSS questions there is.

For years the answer was a set of ::-webkit-scrollbar pseudo-elements that only worked in some browsers. Today there's a standardized approach, the CSS Scrollbars Styling module with scrollbar-color and scrollbar-width, alongside the older WebKit pseudo-elements that still have their uses. This guide explains both, shows how they interact, and gives you a pattern that behaves predictably across browsers.

Two Approaches, One Goal

There are two ways to style scrollbars:

  1. The standard properties : scrollbar-color and scrollbar-width. They're simple, limited on purpose, and designed so the browser stays in control of accessibility and platform behavior.
  2. The legacy ::-webkit-scrollbar pseudo-elements : A nonstandard set of selectors that let you style every part of the scrollbar: track, thumb, buttons, and corner. They're far more flexible, but they were never standardized.

The key thing to understand is that you'll often want both, and the order in which browsers apply them matters. We'll get to that after covering each one.

The Standard Way: scrollbar-color and scrollbar-width

scrollbar-color

This property takes two colors: the thumb (the part you drag) followed by the track (the groove it slides in).

.sidebar {
  overflow-y: auto;
  scrollbar-color: #64748b #1e293b;
}

You can also use auto to reset to the platform default. There's no way to set just the thumb; if you want a transparent track, say so explicitly:

.panel {
  scrollbar-color: rgb(100 116 139 / 0.6) transparent;
}

scrollbar-width

This controls thickness, but only with keywords, not lengths:

  • auto : the platform default.
  • thin : a thinner variant, if the platform provides one.
  • none : hides the scrollbar while keeping the element scrollable.
.code-block {
  overflow-x: auto;
  scrollbar-width: thin;
  scrollbar-color: #475569 transparent;
}

You can't set an exact pixel width, and that's deliberate. Tiny scrollbars are hard to grab for people with motor impairments, and a keyword lets each platform pick a size that still works.

Inheritance

scrollbar-color is inherited, so setting it on :root styles every scrollbar on the page, including the main viewport scrollbar:

:root {
  scrollbar-color: #94a3b8 #f1f5f9;
}

@media (prefers-color-scheme: dark) {
  :root {
    scrollbar-color: #475569 #0f172a;
  }
}

scrollbar-width is not inherited, so apply it to each scroll container that needs it, or to * if you truly want it everywhere.

Browser support for the standard properties

Firefox has supported both properties for a long time, and Chromium-based browsers (Chrome, Edge, Opera, and others) added them more recently. Safari supports scrollbar-width, but its scrollbar-color support has lagged behind the others; check caniuse for the current state before relying on it. That gap is the main reason the WebKit pseudo-elements are still worth including.

The Legacy Way: ::-webkit-scrollbar

The WebKit pseudo-elements work in Safari and in Chromium-based browsers. Firefox has never supported them.

.sidebar::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

.sidebar::-webkit-scrollbar-track {
  background: #1e293b;
}

.sidebar::-webkit-scrollbar-thumb {
  background: #64748b;
  border-radius: 999px;
  border: 2px solid #1e293b;
}

.sidebar::-webkit-scrollbar-thumb:hover {
  background: #94a3b8;
}

.sidebar::-webkit-scrollbar-corner {
  background: #1e293b;
}

Here's what each part does:

  • ::-webkit-scrollbar : the scrollbar as a whole. Setting width (for vertical bars) or height (for horizontal bars) is what "activates" custom styling. Without it, the other pseudo-elements are ignored.
  • ::-webkit-scrollbar-track : the groove.
  • ::-webkit-scrollbar-thumb : the draggable handle.
  • ::-webkit-scrollbar-corner : the small square where vertical and horizontal scrollbars meet.
  • ::-webkit-scrollbar-button : the arrow buttons at each end, which many custom designs hide.

The border trick on the thumb is a classic: a border in the same color as the track creates the illusion of padding around the thumb, so it looks like a floating pill.

How the Two Approaches Interact

This is the part that trips people up. In Chromium-based browsers, if an element has scrollbar-color or scrollbar-width set to anything other than auto, the ::-webkit-scrollbar styles are ignored for that element. The standard properties win.

That means a stylesheet that does this:

.sidebar {
  scrollbar-width: thin;
  scrollbar-color: #64748b #1e293b;
}

.sidebar::-webkit-scrollbar-thumb {
  border-radius: 999px;
}

will get the standard thin scrollbar in Chrome, and the rounded thumb will never appear there. In Safari, which applies the pseudo-elements, you'll see the WebKit styles.

If you want the richer WebKit styling in Chromium and Safari and the standard properties everywhere else, apply the standard properties only where the pseudo-elements aren't supported:

.sidebar {
  overflow-y: auto;
}

/* Browsers without ::-webkit-scrollbar (Firefox) */
@supports not selector(::-webkit-scrollbar) {
  .sidebar {
    scrollbar-width: thin;
    scrollbar-color: #64748b #1e293b;
  }
}

/* Chromium and Safari */
.sidebar::-webkit-scrollbar {
  width: 10px;
}

.sidebar::-webkit-scrollbar-track {
  background: #1e293b;
}

.sidebar::-webkit-scrollbar-thumb {
  background: #64748b;
  border-radius: 999px;
  border: 2px solid #1e293b;
}

The @supports selector() function tests whether the browser understands a given selector. Firefox doesn't recognize ::-webkit-scrollbar, so it applies the standard properties. Chromium and Safari skip that block and use the pseudo-elements.

The opposite approach is also valid and more future-proof: use only the standard properties, and accept that Safari may show its default scrollbar. As browser support for scrollbar-color fills in, that stylesheet will simply start working everywhere without changes. Which one you choose depends on how much the exact look matters to you.

A Reusable Theme with Custom Properties

Wrapping your scrollbar colors in custom properties keeps both code paths in sync:

:root {
  --sb-thumb: #94a3b8;
  --sb-thumb-hover: #64748b;
  --sb-track: #f1f5f9;
  --sb-size: 10px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --sb-thumb: #475569;
    --sb-thumb-hover: #64748b;
    --sb-track: #0f172a;
  }
}

.scroll-area {
  overflow: auto;
}

@supports not selector(::-webkit-scrollbar) {
  .scroll-area {
    scrollbar-color: var(--sb-thumb) var(--sb-track);
    scrollbar-width: thin;
  }
}

.scroll-area::-webkit-scrollbar {
  width: var(--sb-size);
  height: var(--sb-size);
}

.scroll-area::-webkit-scrollbar-track {
  background: var(--sb-track);
}

.scroll-area::-webkit-scrollbar-thumb {
  background: var(--sb-thumb);
  border-radius: 999px;
  border: 2px solid var(--sb-track);
}

.scroll-area::-webkit-scrollbar-thumb:hover {
  background: var(--sb-thumb-hover);
}

Now changing a theme means changing four variables, not hunting through two sets of rules.

Hiding Scrollbars (and When Not To)

Sometimes you want an element to scroll without showing a scrollbar, such as a horizontally swipeable row of chips on mobile. The standard way is:

.chip-row {
  display: flex;
  gap: 0.5rem;
  overflow-x: auto;
  scrollbar-width: none;
}

/* Older Safari fallback */
.chip-row::-webkit-scrollbar {
  display: none;
}

Use this sparingly. A scrollbar is often the only visual cue that there's more content. If you hide it, provide another cue, such as a fade at the edge, a partially cut-off last item, or arrow buttons:

.chip-row {
  mask-image: linear-gradient(to right, #000 85%, transparent);
}

Also, never hide the scrollbar on the main page or on long content panels. Mouse users who rely on dragging the scrollbar will lose that option, and people with some cognitive disabilities depend on it to understand how long a page is.

Preventing Layout Shift with scrollbar-gutter

On platforms with classic, space-taking scrollbars (most Windows and Linux setups, and macOS when "Always show scroll bars" is on), content jumps sideways when a scrollbar appears. It's especially noticeable when a modal opens and you lock page scrolling, or when a list grows long enough to need a scrollbar.

scrollbar-gutter reserves space for the scrollbar ahead of time:

html {
  scrollbar-gutter: stable;
}

.results-panel {
  overflow-y: auto;
  scrollbar-gutter: stable;
}

With stable, the space is reserved whether or not the element currently overflows. The both-edges keyword reserves matching space on the opposite side too, which keeps content visually centered:

.centered-panel {
  overflow: auto;
  scrollbar-gutter: stable both-edges;
}

Note that scrollbar-gutter has no effect on overlay scrollbars, the kind that float above content on macOS and mobile by default, because they don't take up layout space. It's supported in all current major browsers, though Safari was the last to add it, so check support if you target older versions.

Platform Differences You Can't Style Away

Even with perfect CSS, scrollbars won't look identical everywhere:

  • Overlay scrollbars : macOS (by default), iOS, and Android use scrollbars that appear only while scrolling and overlay content. Some browsers ignore custom widths or track colors for them.
  • System settings : Users can force scrollbars to always show, change their size, or enable high contrast modes. In Windows high contrast (forced colors) mode, custom scrollbar colors are generally overridden by the system palette, which is the right behavior.
  • Touch devices : Mobile browsers largely ignore scrollbar styling, since the indicator is a temporary overlay.

Design your scrollbars as an enhancement, not a requirement. The page should look fine with the default system scrollbar.

Accessibility Checklist

Custom scrollbars can hurt usability if you're not careful. Keep these rules in mind:

  1. Keep enough contrast. The thumb should be clearly visible against the track. A 3:1 contrast ratio, the WCAG guideline for non-text UI components, is a good target.
  2. Don't make it too thin. With the WebKit pseudo-elements, you can set width: 2px, but you shouldn't. Stay around 8px to 12px for desktop.
  3. Keep a hover state. A thumb that darkens or grows on hover gives mouse users confirmation they're about to grab it.
  4. Don't hide scrollbars on essential content. Horizontal chip rows are fine; articles, tables, and sidebars are not.
  5. Respect forced colors. Don't fight the system in high contrast mode.
@media (forced-colors: active) {
  .scroll-area {
    scrollbar-color: auto;
  }
}

Common Pitfalls

  1. WebKit styles "stopped working" in Chrome : You probably added scrollbar-color or scrollbar-width somewhere, possibly through a global reset or a framework utility. Remember that in Chromium the standard properties override the pseudo-elements.
  2. Pseudo-element styles do nothing : You didn't set a width or height on ::-webkit-scrollbar. That declaration is what enables custom rendering.
  3. scrollbar-width set on the wrong element : It isn't inherited. Set it on the element that actually scrolls, the one with overflow: auto or overflow: scroll.
  4. Styling the page scrollbar on body : The main viewport scrollbar belongs to the root element in standards mode, so set scrollbar-color on :root or html.
  5. Expecting pixel control from the standard : scrollbar-width only takes keywords. If you need an exact size, the WebKit pseudo-elements are the only option, and only in the browsers that support them.

Conclusion

Scrollbar styling has finally settled into a clear picture. The standard scrollbar-color and scrollbar-width properties are the future-proof foundation, supported in Firefox and Chromium and partially in Safari. The ::-webkit-scrollbar pseudo-elements still give you finer control in Chromium and Safari, but in Chromium they step aside whenever the standard properties are set. And scrollbar-gutter quietly solves the layout shift problem that has annoyed developers for years.

Pick the approach that fits your needs, keep your colors in custom properties, test on a platform with classic scrollbars, and never let style get in the way of the scrollbar's real job: showing people where they are and letting them move.

Tags :
Share :

Related Posts

A Complete Guide to CSS Container Queries

A Complete Guide to CSS Container Queries

For more than a decade, responsive design meant one thing: media queries. You asked the browser how wide the viewport was and adjusted your layout ac

Continue Reading
A Comprehensive Guide to Installing Next.js

A Comprehensive Guide to Installing Next.js

Next.js has emerged as a powerful framework for building React applications, offering features like server-side rendering, static site generation, an

Continue Reading
Advanced CSS with clamp(), min(), and max(): Simplifying Dynamic Styling

Advanced CSS with clamp(), min(), and max(): Simplifying Dynamic Styling

CSS has evolved significantly, and modern tools like clamp(), min(), and max() are powerful game-changers in dynamic styling. If you’ve struggl

Continue Reading