Skip to content

Scroll Area

A scrollable region with a thin themed scrollbar, reachable by keyboard, with optional fades at the edges that still have content.

Displayv0.1.0experimentalWCAG 2.2 AAView spec
  • Deployment 1
  • Deployment 2
  • Deployment 3
  • Deployment 4
  • Deployment 5
  • Deployment 6
  • Deployment 7
  • Deployment 8
  • Deployment 9
  • Deployment 10
  • Deployment 11
  • Deployment 12
  • Deployment 13
  • Deployment 14
  • Deployment 15
  • Deployment 16
  • Deployment 17
  • Deployment 18
  • Deployment 19
  • Deployment 20
import { ScrollArea } from "@rdloom/react";

const items = Array.from({ length: 20 }, (_, i) => `Deployment ${i + 1}`);

export default function ScrollAreaVerticalListExample() {
  return (
    <div className="flex w-full justify-center">
      <ScrollArea label="Recent deployments" className="h-56 w-full max-w-sm border border-[var(--rd-color-border-default)]">
        <ul className="flex flex-col">
          {items.map((name) => (
            <li key={name} className="border-b border-[var(--rd-color-border-default)] px-4 py-3 text-sm last:border-b-0">
              {name}
            </li>
          ))}
        </ul>
      </ScrollArea>
    </div>
  );
}

Installation

npx rdloom add scroll-area

Copies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes.

Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/scroll-area.json

Works, but without upgrade tracking.

Usage

import { ScrollArea } from "@rdloom/react";

<div className="flex w-full justify-center">
  <ScrollArea label="Recent deployments" className="h-56 w-full max-w-sm border border-[var(--rd-color-border-default)]">
    <ul className="flex flex-col">
      {items.map((name) => (
        <li key={name} className="border-b border-[var(--rd-color-border-default)] px-4 py-3 text-sm last:border-b-0">
          {name}
        </li>
      ))}
    </ul>
  </ScrollArea>
</div>

Horizontal tags

  • Design
  • Engineering
  • Marketing
  • Finance
  • Support
  • Legal
  • People
  • Security
  • Data
  • Sales
  • Research
import { ScrollArea } from "@rdloom/react";

const tags = ["Design", "Engineering", "Marketing", "Finance", "Support", "Legal", "People", "Security", "Data", "Sales", "Research"];

export default function ScrollAreaHorizontalTagsExample() {
  return (
    <div className="flex w-full justify-center">
      <ScrollArea label="Teams" orientation="horizontal" className="w-[24rem] max-w-full">
        <ul className="flex w-max gap-2 pb-3">
          {tags.map((tag) => (
            <li key={tag} className="whitespace-nowrap rounded-full border border-[var(--rd-color-border-default)] px-3 py-1 text-sm">
              {tag}
            </li>
          ))}
        </ul>
      </ScrollArea>
    </div>
  );
}

Both directions

NameRegionPlanSeatsOwnerCreatedRenewsStatus
Name 1Region 1Plan 1Seats 1Owner 1Created 1Renews 1Status 1
Name 2Region 2Plan 2Seats 2Owner 2Created 2Renews 2Status 2
Name 3Region 3Plan 3Seats 3Owner 3Created 3Renews 3Status 3
Name 4Region 4Plan 4Seats 4Owner 4Created 4Renews 4Status 4
Name 5Region 5Plan 5Seats 5Owner 5Created 5Renews 5Status 5
Name 6Region 6Plan 6Seats 6Owner 6Created 6Renews 6Status 6
Name 7Region 7Plan 7Seats 7Owner 7Created 7Renews 7Status 7
Name 8Region 8Plan 8Seats 8Owner 8Created 8Renews 8Status 8
Name 9Region 9Plan 9Seats 9Owner 9Created 9Renews 9Status 9
Name 10Region 10Plan 10Seats 10Owner 10Created 10Renews 10Status 10
Name 11Region 11Plan 11Seats 11Owner 11Created 11Renews 11Status 11
Name 12Region 12Plan 12Seats 12Owner 12Created 12Renews 12Status 12
Name 13Region 13Plan 13Seats 13Owner 13Created 13Renews 13Status 13
Name 14Region 14Plan 14Seats 14Owner 14Created 14Renews 14Status 14
Name 15Region 15Plan 15Seats 15Owner 15Created 15Renews 15Status 15
Name 16Region 16Plan 16Seats 16Owner 16Created 16Renews 16Status 16
import { ScrollArea } from "@rdloom/react";

const columns = ["Name", "Region", "Plan", "Seats", "Owner", "Created", "Renews", "Status"];
const rows = Array.from({ length: 16 }, (_, i) => i + 1);

export default function ScrollAreaBothDirectionsExample() {
  return (
    <div className="flex w-full justify-center">
      <ScrollArea label="Accounts table" orientation="both" className="h-56 w-full max-w-md border border-[var(--rd-color-border-default)]">
        <table className="w-max min-w-full border-collapse text-sm">
          <thead>
            <tr>
              {columns.map((c) => (
                <th key={c} scope="col" className="whitespace-nowrap px-4 py-2 text-start font-medium">
                  {c}
                </th>
              ))}
            </tr>
          </thead>
          <tbody>
            {rows.map((r) => (
              <tr key={r} className="border-t border-[var(--rd-color-border-default)]">
                {columns.map((c) => (
                  <td key={c} className="whitespace-nowrap px-4 py-2">
                    {c} {r}
                  </td>
                ))}
              </tr>
            ))}
          </tbody>
        </table>
      </ScrollArea>
    </div>
  );
}

API Reference

Defined by the spec. Components also accept the props of the React Aria component they wrap.

PropTypeDefault
orientation

The directions that scroll.

"vertical" | "horizontal" | "both""vertical"
label

Accessible name of the region. Required unless aria-labelledby points at a heading.

stringnone
showFades

Fade the edges that still have content beyond them.

booleantrue
childrenrequired

The scrolling content.

nodenone

Accessibility

Role region, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.

Keyboard

  • Tab moves focus to the region
  • Arrow keys, Page Up, Page Down, Home and End scroll it

Screen readers announce

  • Announced as a region with its label

What your code must do

  • The region is focusable (tabIndex 0) so keyboard users can scroll it
  • The region has an accessible name from label or aria-labelledby
  • A visible focus ring shows when it has keyboard focus
  • Edge fades are decoration and never hide content permanently

Guidelines

Use it when

  • A fixed-height list, log or table that scrolls inside the page
  • A row of tags or chips wider than its container

Avoid it when

  • The whole page scrolls: leave it to the browser
  • Content that fits: do not add a scroller

Don't

  • Nesting scroll areas in the same direction
  • Hiding the scrollbar entirely
  • A region with no label

Design tokens

The semantic tokens this component uses. Change them once and every component follows; see Design tokens.

  • --rd-color-border-strong
  • --rd-color-focus-ring
  • --rd-radius-control