Custom Scrollbar CSS Generator

Style a scrollbar with the standard two properties and the WebKit pseudo-elements, previewed on a box that actually scrolls in both directions.

Enable JavaScript to change the scrollbar; the CSS below is for the defaults.

Standard width

All the standard property offers. "none" hides the bar while the content still scrolls, which is a decision rather than a style.

Sets width for a vertical bar and height for a horizontal one. Both are written, because setting only width leaves a sideways scroller at its default.

Written as a transparent border with background-clip, so the thumb looks inset while the whole width stays grabbable.

Only written when it differs from the thumb colour. There is no hover state in the standard properties.

Scroll this box. A scrollbar is a control before it is a decoration: it says how much more there is, and where you are in it.

On macOS, scrollbars are hidden until you scroll unless a mouse is attached, so a design that depends on the bar being visible is a design most Mac users never see.

A thin scrollbar is a smaller target for anybody dragging it, which is the trade being made when the width goes down.

Overlay scrollbars sit on top of the content; classic ones take space from it, which is why a layout can shift when the bar appears.

The vertical bar takes its size from width, and the horizontal one from height. Setting only width leaves a horizontal bar at its default size.

Card 1
Card 2
Card 3
Card 4
Card 5
Card 6
Card 7
Card 8

The second box scrolls sideways, where the bar takes its size from height rather than width.

scrollbar.css
/* The standard properties first, so a browser that understands both
   follows this rather than the WebKit pseudo-elements below. */
.scroller {
	scrollbar-width: thin;
	scrollbar-color: rgb(138 138 153) rgb(241 241 244);
}

/* Not standard, never will be, and still what older Chrome and Safari
   understand. */
.scroller::-webkit-scrollbar {
	width: 10px;
	height: 10px;
}

.scroller::-webkit-scrollbar-track {
	background: rgb(241 241 244);
	border-radius: 8px;
}

.scroller::-webkit-scrollbar-thumb {
	background: rgb(138 138 153);
	border-radius: 8px;
	border: 2px solid transparent;
	background-clip: content-box;
}

.scroller::-webkit-scrollbar-thumb:hover {
	background: rgb(90 90 107);
	background-clip: content-box;
}

There are two ways to style a scrollbar, and for a while yet you need both.

The standard properties, scrollbar-width and scrollbar-color, are two lines and work in Firefox, in Chrome and Edge 121, and in Safari 18.2. They cannot do much: a width from a list of three, a thumb colour and a track colour. No radius, no hover state, no inset.

The WebKit pseudo-elements are not standard and never will be, and they are what every older Chrome and Safari understands. They can do anything, including the thing that makes a custom scrollbar look designed rather than recoloured: a transparent border with background-clip: content-box, which insets the thumb from the track without shrinking the area you can grab.

This writes both, standard first, so a browser that understands both follows the standard.

How to use

  1. Pick the standard width, then the WebKit size in pixels. Both are written.
  2. Set the colours. The hover colour only appears in the output when it differs from the thumb colour, since there is no hover state in the standard properties.
  3. Scroll the preview boxes. The second one scrolls sideways, where the bar takes its size from height rather than width.

Example

A 10px bar with rounded corners and an inset thumb:

/* The standard properties first, so a browser that understands both
   follows this rather than the WebKit pseudo-elements below. */
.scroller {
	scrollbar-width: thin;
	scrollbar-color: rgb(138 138 153) rgb(241 241 244);
}

/* Not standard, never will be, and still what older Chrome and Safari
   understand. */
.scroller::-webkit-scrollbar {
	width: 10px;
	height: 10px;
}

.scroller::-webkit-scrollbar-track {
	background: rgb(241 241 244);
	border-radius: 8px;
}

.scroller::-webkit-scrollbar-thumb {
	background: rgb(138 138 153);
	border-radius: 8px;
	border: 2px solid transparent;
	background-clip: content-box;
}

.scroller::-webkit-scrollbar-thumb:hover {
	background: rgb(90 90 107);
	background-clip: content-box;
}

Choosing “none” replaces all of it with a hidden bar and a comment explaining what that costs, because hiding a scrollbar is a decision rather than a style.

Pitfalls

Hiding a scrollbar does not remove the scrolling. The content still moves, and nothing says so any more. Keyboard and trackpad users are fine; anyone looking for a bar, or dragging one, is not. Hide it only where another affordance shows there is more to see.

Thin is a smaller target. Somebody dragging a scrollbar with a mouse has less to aim at. That is the trade, and it is worth making deliberately rather than because thin looks tidier in a screenshot.

Most Mac users never see your work. macOS hides scrollbars until you scroll unless a mouse is attached, so a design that depends on the bar being visible is invisible to a large share of the audience.

Set both width and height. The vertical bar takes its size from width and the horizontal one from height. Setting only width leaves a sideways scroller at the browser default, which looks like a bug.

Contrast applies here too. A thumb that barely differs from the track is a control nobody can find. It is a UI element, not a decoration, and the usual contrast minimum applies.

Styling the page scrollbar affects the whole page. Put the rules on the element that scrolls. Applying them to html or body changes the window scrollbar, which is rarely what a component wanted.

::-webkit-scrollbar and scrollbar-color conflict where both work. In current Chrome, the standard property wins when both are set on the same element, which is the behaviour this output relies on.

Compatibility

scrollbar-width and scrollbar-color work in Firefox 64, Chrome and Edge 121, and Safari 18.2. Before those versions, only the WebKit pseudo-elements did anything, which is why both are written.

::-webkit-scrollbar works in Chrome, Edge, Safari and every Chromium browser, and has never worked in Firefox. It is not on a standards track, and Chrome’s own guidance is to prefer the standard properties where they suffice.

The preview cannot use inline styles for the scrollbar, because the pseudo-elements only exist in a stylesheet, so the block passes the values as custom properties and the stylesheet reads them. That is also why the preview can only show a subset of what the copied CSS does.

The stylesheet is built by one function and mirrored in PHP for the first paint, with a case file comparing the two on every build: the default, a hidden bar, an auto-width bar with matching hover, and an invalid keyword with a negative size.

Frequently asked questions

Why are there two syntaxes?
Because WebKit shipped pseudo-elements years before the CSS Working Group standardised anything, and the standard that eventually arrived deliberately offers less. Both are in use today.
How do I hide a scrollbar but keep scrolling?
Set the width to none, which writes scrollbar-width: none and ::-webkit-scrollbar { display: none }. Then add something else that shows the content continues: a fade at the edge, a partly visible next item, or arrows.
Can I style the scrollbar of the whole page?
Yes, by using html or body as the selector. Be careful: it is one of the few pieces of UI a user may have deliberately configured in their operating system.
Why does my rounded thumb look square?
border-radius on the thumb needs the track to be visible around it. Add an inset, which is the transparent border trick, and the rounding becomes visible.
Does any of this work in Firefox?
Only the standard properties, which is exactly what they are for. Firefox has never supported the WebKit pseudo-elements and has said it will not.
Weekly drops

New tools, when there are new tools

One email when something worth using ships. No schedule to fill, so no filler.

Your address goes nowhere else, and one click unsubscribes.