MD3
Expressive
MATERIAL DESIGN 3 EXPRESSIVE

Scroll Area

Custom scrollbar component that provides a consistent, MD3 Expressive look and feel across all browsers.

The Scroll Area component augments native scroll functionality with a minimal, pill-shaped scrollbar that follows Material Design 3 Expressive guidelines. It ensures that scrollbars look and behave identically across different operating systems and browsers.

Loading demo...

Introduction

Native scrollbars often conflict with modern UI designs, appearing bulky or inconsistent across platforms. The MD3 Expressive Scroll Area solves this by providing a highly customizable, themeable scrollbar that only appears when needed. It is built on top of Radix UI's Scroll Area primitives, ensuring high performance and excellent accessibility.

Anatomy

  • Viewport: The visible area containing the scrollable content.
  • Scrollbar: The track and thumb used to navigate the content.
  • Thumb: The pill-shaped indicator that moves as the user scrolls.
  • Corner: The intersection where both horizontal and vertical scrollbars meet.

Variants

Orientations

  • Vertical (Default): For content that exceeds the height of its container.
  • Horizontal: For wide content like data tables or image galleries.
  • Both: Enables both vertical and horizontal scrolling with a dedicated corner element.
Loading demo...

Features

Visibility Behaviors

Control when the scrollbar is displayed using the type prop:

  • Scroll (Default): Appears while scrolling and hides after a delay. Recommended for all devices and touch interactions.
  • Hover: Scrollbar appears only when hovering over the container (desktop-focused).
  • Always: Scrollbar is permanently visible.
  • None: Hides scrollbar UI completely while keeping native scrolling enabled.
Loading demo...

Custom Sizing & Colors

You can flexibly customize the thickness, colors, and border radius of the scrollbars using dedicated styling props:

  • scrollbarSize: Set scrollbar track thickness in pixels (e.g. 6 for slim, 14 for high visibility).
  • thumbClassName: Additional Tailwind CSS classes applied to the thumb element (overrides background colors, hover/active states, and border radius via tailwind-merge).
  • trackClassName: Tailwind CSS classes applied to the scrollbar track container.
  • cornerClassName: Tailwind CSS classes applied to the corner element when both orientations are active.
  • verticalScrollbarProps & horizontalScrollbarProps: Override styling and size individually per scroll axis.
Loading demo...

Expressive Styling

The scrollbar thumb is a pill-shaped element styled with Material Design 3 surface tokens (bg-m3-on-surface/25). It smoothly transitions to darker contrast on hover (hover:bg-m3-on-surface/40) and active drag (active:bg-m3-on-surface/55), while providing an accessible 44px touch target.

Usage

Basic Usage

import { ScrollArea } from "@bug-on/m3-expressive/layout";

<ScrollArea className="h-72 w-48 rounded-md border">
  <div className="p-4">
    <h4>Tags</h4>
    {tags.map((tag) => (
      <div key={tag} className="text-sm">
        {tag}
      </div>
    ))}
  </div>
</ScrollArea>

Custom Sizing and Colors

import { ScrollArea } from "@bug-on/m3-expressive/layout";

<ScrollArea
  scrollbarSize={8}
  thumbClassName="bg-m3-primary/60 hover:bg-m3-primary rounded-full"
  trackClassName="bg-m3-surface-container"
  className="h-64 w-80 rounded-m3-md border border-m3-outline-variant"
>
  <div className="p-4">
    {/* Scrollable content */}
  </div>
</ScrollArea>

Custom Scrollbar Composition

You can compose custom layouts directly using ScrollArea, ScrollAreaScrollbar, and ScrollAreaCorner:

import {
  ScrollArea,
  ScrollAreaScrollbar,
  ScrollAreaCorner,
} from "@bug-on/m3-expressive/layout";

<ScrollArea orientation="both" className="h-64 w-full">
  <div className="w-[1200px] h-[800px] p-4">Large canvas content</div>
  <ScrollAreaScrollbar orientation="vertical" scrollbarSize={8} />
  <ScrollAreaScrollbar orientation="horizontal" scrollbarSize={8} />
  <ScrollAreaCorner />
</ScrollArea>

Best Practices

Do

  • Use Scroll Area for containers with a fixed height where content may overflow.
  • Prefer type="scroll" for smooth, native-like mobile and desktop scrolling.
  • Use type="always" for critical scrollable areas (like sidebars or code blocks) where users need immediate visual orientation.
  • Use scrollbarSize and semantic MD3 tokens (bg-m3-primary, bg-m3-surface-container) to match the surrounding layout theme.
  • Set a clear max-height or height on the Scroll Area container.

Don't

  • Don't use Scroll Area for the entire page body; let the browser handle top-level scrolling.
  • Don't use orientation="both" if only one axis is likely to overflow.
  • Avoid nesting multiple Scroll Areas within each other, as it can be confusing for users to navigate.

Accessibility

  • Roles: Correctly implements ARIA roles for scroll containers and regions.
  • Keyboard: Supports standard keyboard navigation (Arrow keys, Page Up/Down, Home/End).
  • Touch: Provides native-feeling momentum scrolling on touch devices with accessible 44px min touch target on thumb.
  • Visibility: The scrollbar is a visual aid and doesn't replace native accessibility features.

API Reference

ScrollArea

PropTypeDefaultDescription
type"scroll" | "hover" | "always" | "none""scroll"Visibility behavior of scrollbars.
orientation"vertical" | "horizontal" | "both""vertical"Scroll axis to render.
scrollbarSizenumber10Thickness of scrollbar track in px (width for vertical, height for horizontal).
thumbClassNamestring—Tailwind CSS classes for the thumb element (merges via tailwind-merge).
trackClassNamestring—Tailwind CSS classes for the scrollbar track container.
cornerClassNamestring—CSS classes for the corner element when both scrollbars are visible.
verticalScrollbarPropsOmit<ScrollAreaScrollbarProps, "orientation">—Props forwarded specifically to the vertical scrollbar.
horizontalScrollbarPropsOmit<ScrollAreaScrollbarProps, "orientation">—Props forwarded specifically to the horizontal scrollbar.
thumbPropsComponentPropsWithoutRef<typeof RadixScrollArea.Thumb>—Extra props forwarded directly to inner thumb elements.
scrollHideDelaynumber600Delay in ms before scrollbars hide when type="scroll" or type="hover".
viewportClassNamestring—Extra classes applied to the inner scrolling viewport element.
viewportRefReact.Ref<HTMLDivElement>—Ref to the scrolling viewport element.
viewportPropsComponentPropsWithoutRef<typeof RadixScrollArea.Viewport>—Extra props applied to the inner viewport.
classNamestring—Custom classes applied to the root container.

ScrollAreaScrollbar

Standalone scrollbar component for custom composition.

PropTypeDefaultDescription
orientation"vertical" | "horizontal""vertical"Scrollbar orientation ("vertical" or "horizontal").
scrollbarSizenumber10Thickness in pixels (width or height).
thumbClassNamestring—Tailwind CSS classes for the thumb element.
trackClassNamestring—Tailwind CSS classes for the track element.
thumbRefReact.Ref<React.ElementRef<typeof RadixScrollArea.Thumb>>—Ref forwarded directly to the inner thumb element.
thumbPropsComponentPropsWithoutRef<typeof RadixScrollArea.Thumb>—Extra props forwarded to the thumb element.
classNamestring—Classes applied to the scrollbar container.

ScrollAreaCorner

Intersection corner component rendered when both scrollbars are active.

PropTypeDefaultDescription
classNamestring—Classes applied to the corner element (default: bg-m3-surface-container).