A drawn rail that reports the scroll position and drags it. Three looks: a hairline that fades when idle, a bar chunky enough to grab, and a rail whose track fills up behind the thumb as a reading-progress meter.
It draws scrolling; it never takes it over. No wheel hijack, no fake transform
scroller — the wheel, the trackpad, Space, Home,
find-in-page and anchor links all behave exactly as before. The thumb itself is a
focusable role="scrollbar", so arrow and page keys move the target once it
has focus. Press H to hide the control panel.
Right-hand side by default. Grab the thumb, click the track to page up
and down, or tab to the thumb and use the arrows. The native bar is hidden while the
rail is up, and comes back the moment you call destroy().
Scroll on. The rail is measured from the real document height, so it
keeps up with images loading late, an accordion opening, or a font swapping in — a
ResizeObserver watches the document and re-measures.
On a coarse-pointer device the rail is not built at all. Phone and tablet users get their platform's own overlay scrollbar, which is thumb-sized, appears on scroll and disappears again — better than anything a page can draw. The gate is a live media query, so plugging in a mouse brings the rail back.
progress 0% · thumb 0px · scrollable 0px
Point init at any scrollable element and the rail is placed
over its box instead of the viewport. Nothing is wrapped, nothing is re-parented — the
rail is fixed and re-measures from the element's rectangle when the page moves.
The first rule of a custom scrollbar is that it must not become the only way to scroll. Hide the native bar if you like, but leave the mechanism alone.
The second is that it must survive a resize. Content height changes constantly — a lazy image lands, a panel opens, the viewport rotates — and a bar measured once at load will lie for the rest of the session.
The third is that a scrollbar is a control, not an ornament. If a pointer can drag it, a keyboard should be able to move it, and a screen reader should be told what it is and where it sits.
The fourth is restraint on touch. A phone already draws a good overlay scrollbar that knows about rubber-banding and safe areas. Replacing it buys nothing and costs a listener on every scroll frame.
The fifth is that the thumb has a minimum size. A very long page produces a thumb three pixels tall, which nobody can grab; clamp it and adjust the mapping so a drag still reaches both ends.
The sixth is that clicking the track should do something sensible. Native bars page up and down. Jump-to-position is fine too — pick one and keep it.
The seventh is that fading the rail out when idle is a taste decision, not an accessibility one. Keep it visible wherever the position itself is information.
The progress preset fills the track behind the thumb and
prints the percentage beside it. Same rail, same drag, one extra element — so an
article page does not need a second widget pinned to the top.
Switch to it in the panel to see the fill and the readout on the page
rail. The percentage is the scrolled fraction, and you can replace the text through
the label option — minutes left, section name, anything.
Everything below is filler so there is something to scroll. Try the keyboard while you are here: Space and Shift+Space, Page Down, Home and End all still work, because the engine never intercepts them.
The rail is one fixed element with a track and a thumb. It costs a passive scroll listener and a couple of style writes per frame — no layout thrash, because the thumb moves with a transform and the measurements are cached until something actually resizes.