Skip to content

PRIMITIVE

Scrollbar

The theme's own scrollbar on an element that already scrolls. Thin, themed, native — and gone entirely on touch, where the system's own bar is already right.

One class apart

Scroll both boxes. On a phone or a tablet they look identical, and that is the design rather than something missing.

WITHOUT

The operating system's own scrollbar, on a surface it has never seen. On Linux and Windows it is a light trough with a pair of arrow buttons; on macOS it is an overlay bar that fades away when you stop.

Nothing here is broken. It is simply the only part of the page the theme does not reach.

Keep scrolling. There is deliberately more text than fits, in both boxes, because a scrollbar that never appears documents nothing at all.

WITH SCROLLBAR

The same box, one class added, nothing else changed. The bar is thin, the thumb is the line colour this system draws its borders with, and the trough is whatever happens to be behind it.

Put the pointer over this box and the thumb brightens. Tab to it — a scroll container with no focusable children is in the tab order — and it brightens the same way, because a reader arriving by keyboard never hovers anything.

It does not make anything scrollable. This box was already a scroller; the class only decides what its bar looks like.

HTML
<!-- Two identical boxes, one class apart. Scroll either one.
     On a touch screen they are identical, deliberately. -->
<div class="hairline-grid" style="--cell: min(100%, 260px)">
  <div class="hairline-cell">
    <div class="section-head">
      <span class="section-head-kicker">WITHOUT</span>
      <span class="section-head-rule"></span>
    </div>
    <div class="flow" style="block-size: 150px; overflow: auto">
      <p>
        The operating system's own scrollbar, on a surface it has never seen. On
        Linux and Windows it is a light trough with a pair of arrow buttons; on
        macOS it is an overlay bar that fades away when you stop.
      </p>
      <p>
        Nothing here is broken. It is simply the only part of the page the theme
        does not reach.
      </p>
      <p>
        Keep scrolling. There is deliberately more text than fits, in both
        boxes, because a scrollbar that never appears documents nothing at all.
      </p>
    </div>
  </div>

  <div class="hairline-cell">
    <div class="section-head">
      <span class="section-head-kicker">WITH SCROLLBAR</span>
      <span class="section-head-rule"></span>
    </div>
    <div class="flow scrollbar" style="block-size: 150px; overflow: auto">
      <p>
        The same box, one class added, nothing else changed. The bar is thin,
        the thumb is the line colour this system draws its borders with, and the
        trough is whatever happens to be behind it.
      </p>
      <p>
        Put the pointer over this box and the thumb brightens. Tab to it — a
        scroll container with no focusable children is in the tab order — and it
        brightens the same way, because a reader arriving by keyboard never
        hovers anything.
      </p>
      <p>
        It does not make anything scrollable. This box was already a scroller;
        the class only decides what its bar looks like.
      </p>
    </div>
  </div>
</div>

Reference

Read from primitives/scrollbar.css when this page was built, so it cannot fall behind the stylesheet.

CLASSES.scrollbar
READS--ds-scroll-thumb--ds-scroll-thumb-hover--ds-scroll-track

Notes

<!-- The element must ALREADY scroll. This class only decides
     what its bar looks like — it sets no overflow of its own. -->
<div class="gallery-track scrollbar">…</div>

TOUCH IS LEFT ENTIRELY ALONE, AND THAT IS THE FIRST DECISION

A phone draws an overlay scrollbar that already looks right, costs no layout and fades on its own. There is nothing here worth improving and a great deal worth breaking. So the whole rule sits inside @media (pointer: fine), and this class is a no-op on every touch device — measured on Pixel 7, iPhone 14 and iPad, all of which report pointer: fine as false and keep scrollbar-width: auto.

It is not a width query, so the breakpoint-free rule is intact. prefers-reduced-motion in base.css is the same shape: a question about the reader, never about the window.

Why not ::-webkit-scrollbar

It is the better-known technique, it offers far more control — a radius, a width in pixels, margins, corners — and it was measured and rejected on all three engines. Chromium 151, Firefox 153 and WebKit 26.5, headed, reading pixels rather than documentation.

It is ignoredThe moment any standard property is set on the same element, Chrome and Safari drop the pseudo-element entirely. A webkit rule painting the bar green, beside scrollbar-color, painted nothing at all. You cannot have both.
In Firefox it is worse than inertA ::-webkit-scrollbar rule carrying a width flips Firefox from an overlay bar to a classic one: it takes 12px of layout away from the content and paints a native grey bar, ignoring every colour given to it. The value is ignored; only the rule's presence matters.
It overrides the readerIt forced Safari out of the macOS overlay scrollbar into a permanent, space-taking one. The standard properties change how a scrollbar looks; that pseudo-element changes what kind of scrollbar somebody gets — which is a system preference, not ours.

Where it goes

On the scroller itself. Both properties inherit, so putting it on a wrapper themes every scroll container inside — occasionally useful, and never what you want on the shell.

The gallery's lightbox carries it, which is the reason it exists. Nothing else in the library scrolls today — modal-body is overflow: auto but no specimen is long enough to show a bar. The first modal with more content than height wants this class beside modal-body, for exactly the reason the lightbox did.

What the caller sets

Nothing, normally. A theme moves all three at once, and the trough is transparent so a scroller on a raised surface needs no rule of its own.

--ds-scroll-thumb:       var(--ds-rule-strong);  /* at rest */
--ds-scroll-thumb-hover: var(--ds-ink-faint);    /* hover, focus-within */
--ds-scroll-track:       transparent;            /* the trough */

THERE IS NO WIDTH TOKEN, AND THERE CANNOT BE ONE

scrollbar-width takes auto, thin or none and no length at all. In Chromium thin measured 10px against auto's 15px, so the content box gains 5px when this class is added. That is a layout change rather than a repaint, and it is the one thing to know before adding it to something already tuned.

THERE IS NO scrollbar-none, ON PURPOSE

Hiding a scrollbar removes the only indication that there is more to see. In the gallery it would delete one of the three documented ways to reach every picture — the arrows, the swipe, and the bar. A variant with no consumer is not shipped here, and a variant that removes an affordance needs a better argument than tidiness.