Detached Scrollbar

A custom scrollbar decoupled from the element it scrolls. Zero dependencies. ~2 KB.

npm install detached-scrollbar

Human Eras Timeline

You Are Here
ratio: 0.000 position: 0px

Horizontal Gallery

Vertical Text

The Detached Scrollbar Pattern

A detached scrollbar is a scrollbar that lives in a completely separate part of the DOM from the content it controls. The two are linked by a single shared value: a ratio between 0 and 1.

When the user drags the thumb, the ratio is derived from the thumb's position. That ratio is then applied to compute the content's offset. The content doesn't "scroll" in the native sense — it's absolutely positioned, and its top or left property is updated.

The Two Equations

thumbPosition = ratio * (trackSize - thumbSize)

contentPosition = -ratio * (contentSize - viewportSize)

These two equations are the entire scrollbar. Every feature — dragging, keyboard navigation, programmatic scrolling, resize handling — is just these equations applied from different starting points.

Why Not Native?

Native scrollbars are attached to the element they scroll. You can't place them elsewhere in the DOM. You can't fully style them cross-browser. You can't animate them or control step sizes. A detached scrollbar gives you total control.

Input Methods

Mouse drag, touch drag, keyboard arrows, Home/End keys, and click-on-track-to-jump are all supported out of the box. The thumb receives focus on grab so keyboard works immediately after a mouse interaction.

Accessibility

The thumb has role="slider", aria-valuemin, aria-valuemax, and aria-valuenow attributes. It's focusable via tabindex and responds to standard keyboard patterns.

Multiple Content Elements + Callbacks

Drag the scrollbar to see callbacks fire...