Overlays & Feedback

Custom Scrollbar

A customizable animated scrollbar, with the thumb tracking the scroll on a spring, draggable, and click-the-track to jump.

A custom scrollbar driven by a motion/react spring: the native scrollbar is hidden and replaced with a thumb that can be dragged, jumped to by clicking the track, and that supports gradients and autoHide, with a touch of momentum feedback while scrolling.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add custom-scrollbar.en

New project or installation trouble? Start here →

Custom Scrollbar is a React custom scrollbar component built with Tailwind CSS and Motion. It wraps a fixed-height scroll area and hides the browser's native scrollbar; scrolling is still handled natively, and only the thumb is replaced with one whose color and width you control.

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

300
8
Active edited usageDefault props
<CustomScrollbar/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

When to use

  • Long lists inside a card or sidebar, such as notifications, chat messages, or file lists, where the scrollbar should match the brand color.
  • Dark or heavily designed panels where the native scrollbar looks different on every operating system.
  • Scroll areas inside dashboard widgets, using autoHide so the scrollbar stays out of the way at rest.
  • Not a good fit: full-page scrolling. The component needs a maxHeight on a contained area and only handles the vertical direction.

Installation

npx webberui@latest add https://webberui.com/r/custom-scrollbar.en.json

Or, once registries are configured in components.json, install it as @webberui/custom-scrollbar.

Usage

import { CustomScrollbar } from "@/components/ui/custom-scrollbar";

<CustomScrollbar maxHeight={280} width={9} autoHide>
  <div className="p-5">{/* your long content */}</div>
</CustomScrollbar>

maxHeight decides the height of the content area, and anything beyond it scrolls. The thumb height is calculated automatically from the content ratio. Pass a CSS gradient string to thumbColor to build a colored scrollbar.

Examples

Notification list with an auto-hiding scrollbar

autoHide fades the scrollbar out at rest and brings it back on scroll, hover, or drag.

import { CustomScrollbar } from "@/components/ui/custom-scrollbar";

const notifications = [
  { id: 1, title: "Deployment finished", time: "2 min ago" },
  { id: 2, title: "New member joined the workspace", time: "1 hour ago" },
  { id: 3, title: "Invoice sent", time: "Yesterday" },
  // ...
];

export function NotificationList() {
  return (
    <CustomScrollbar maxHeight={320} autoHide className="border">
      <ul className="divide-y">
        {notifications.map((n) => (
          <li key={n.id} className="flex justify-between px-4 py-3 text-sm">
            <span>{n.title}</span>
            <span className="text-muted-foreground">{n.time}</span>
          </li>
        ))}
      </ul>
    </CustomScrollbar>
  );
}

Solid thumb with a hover color

thumbColor takes any CSS background value, so a plain color string gives a solid thumb; thumbHoverColor sets the hover color, and showTrack={false} removes the track background.

<CustomScrollbar
  maxHeight={240}
  width={6}
  thumbColor="#a1a1aa"
  thumbHoverColor="#52525b"
  showTrack={false}
>
  <nav className="space-y-1 p-3">{/* sidebar links */}</nav>
</CustomScrollbar>

Set the height with a string

maxHeight also accepts a string, such as rem or calc(). Avoid viewport units like vh and dvh.

<CustomScrollbar maxHeight="24rem">
  <div className="p-5">{/* long content */}</div>
</CustomScrollbar>

Props

PropTypeDefaultDescription
childrenReact.ReactNode—The content to wrap and make scrollable
maxHeightnumber | string300Maximum height of the content area; anything beyond it scrolls
widthnumber8Scrollbar width (px)
thumbColorstringindigo-violet gradientThumb color; a CSS gradient string is accepted
thumbHoverColorstring—Thumb color while the pointer is over the scrollbar
trackColorstringrgba(120,120,140,0.14)Track background color
showTrackbooleantrueWhether to show the track background color
autoHidebooleanfalseFades out while idle, and fades in on scroll, hover, or drag

Accessibility

  • When the user has "reduce motion" enabled at the system level, the thumb tracks the scroll instantly with no spring momentum applied
  • Native scrolling behavior is fully preserved (wheel, touch, keyboard); the custom thumb layer carries aria-hidden and is visual feedback only
  • Never use viewport height units such as 100vh; give it an explicit bound through maxHeight instead

FAQ

Does it work with the Next.js App Router?

Yes. The source already has "use client", so import it from a Server Component page directly; all measuring happens inside effects, so the server render never touches window.

Do keyboard and touch scrolling still work?

Yes. The element that actually scrolls is a native overflow-y: auto container, so wheel, touch, and keyboard scrolling are all kept. The custom thumb is a visual layer on top, hidden from assistive technology with aria-hidden.

Does the thumb update when the content height changes?

Yes. A ResizeObserver watches both the container and the content and recalculates the thumb size and position on any change. If the content fits within maxHeight, no scrollbar is shown.

Is horizontal scrolling supported?

No. The scroll area uses overflow-x-hidden, and the component only handles the vertical direction.

How do I change the colors and width?

Use thumbColor, thumbHoverColor, and trackColor for colors (all accept CSS strings, gradients included) and width for the scrollbar width in px (default 8).