# ScrollArea, useScrollEdges and useScrollLock

> The modal's scroll, made portable: thin tinted scrollbars, contained overscroll, edge shadows and a shared scroll lock.

Category: Layout and surfaces. Docs page: https://rime.mjsons.net/components/scroll-area

## Installation

```bash
npx shadcn@latest add KezLahd/rime/scroll-area
```

ScrollArea wraps any scrolling region (a long list in a panel, long terms, a wide table with axis="x") and gives it the modal's scroll: thin native scrollbars in a brand tint (--scrollbar-thumb, --scrollbar-track, --scrollbar-width), overscroll-behavior: contain, and edge shadows that appear only when content has scrolled under the start edge or continues past the end (--scroll-edge-top, --scroll-edge-bottom, and -left/-right for x).

useScrollEdges(ref, { target, axis }) is the engine on its own: a passive scroll listener plus a ResizeObserver that toggle data-scrolled and data-more on the scroller (or a target such as a modal panel) without re-rendering React. Use it when your header and footer are siblings of the scroller and should cast the shadows themselves, as Modal does.

themedScrollClass is just the scrollbar and overscroll half, for a scroller you already have. useScrollLock() locks page scroll with a shared counter (nested overlays nest) and pays the scrollbar's width back as padding so nothing shifts.

## Usage

```tsx
import { ScrollArea, themedScrollClass, useScrollEdges, useScrollLock } from "@/components/ui";

<ScrollArea maxHeight={320} label="Workspace terms">
  {terms}
</ScrollArea>
```

## Props: ScrollArea

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `maxHeight` | `CSSProperties["maxHeight"]` |  | Caps the viewport; the content scrolls past it. Any CSS length. |
| `axis` | `"y" \| "x"` | `"y"` | "y" (default) for lists and long copy, "x" for wide tables. |
| `label` | `string` |  | A name for the scrolling region. When set, the viewport joins the tab order (a keyboard user can scroll it) and is announced as a region. |
| `children` | `ReactNode` |  |  |

Also accepts the native attributes of: AriaAttributes, DOMAttributes, HTMLAttributes.

## Examples

### Vertical

Scroll it: the top shade arrives once you leave the start; the bottom shade leaves at the end.

```tsx
<ScrollArea maxHeight={240} label="Activity">…</ScrollArea>
```

### Horizontal (a wide table)

```tsx
<ScrollArea axis="x" label="Sprints">
  <Table … />
</ScrollArea>
```

### The hook on your own scroller

```tsx
const bodyRef = useRef<HTMLDivElement>(null);
const panelRef = useRef<HTMLDivElement>(null);
useScrollEdges(bodyRef, { target: panelRef });

// CSS: .panel[data-scrolled] .header { box-shadow: var(--scroll-edge-top); }
//      .panel[data-more] .footer { box-shadow: var(--scroll-edge-bottom); }
```

## Theme tokens

System tokens it reads: `--dur-slow`, `--ease`, `--focus-color`, `--scroll-edge-bottom`, `--scroll-edge-left`, `--scroll-edge-right`, `--scroll-edge-top`, `--scrollbar-thumb`, `--scrollbar-track`, `--scrollbar-width`.

## Accessibility

- Pass label to make the region focusable (tabIndex 0) and named, so keyboard users can scroll it; skip it when the content already holds focusable items.
- Native scrollbars stay: they keep platform behaviour (overlay scrollbars on macOS, high contrast in forced-colors).

## Do

- Use it for any region that scrolls inside the page.

## Don't

- Don't build a JavaScript scrollbar.
- Don't nest two vertical scrollers.

Related: https://rime.mjsons.net/components/modal
