# Scroll Area

> A React scroll area with slim custom scrollbars, edge fades that ease out at the ends, snap scrolling and prev / next buttons for rails.

- Type: React component (Layout)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.0.2
- Install: `npx wingo-ui@latest add scroll-area`
- Docs: https://wingo-ui.com/components/scroll-area

## Interaction

Scroll with a wheel, a trackpad or a finger: momentum stays native, the content fades out toward every edge that has more to see, and each fade melts away as you reach the end. With a mouse the slim bars appear on hover and the thumb can be dragged or the track clicked to jump. On a phone the bars show while you scroll and are only indicators, like on iOS and Android, so a swipe near the edge always moves the content. Tab to the area to scroll it with the arrows, Page Up / Down, Home / End and Space; a focus ring and the bars show while it has focus, and a focused rail takes Home, End, Page keys and Space sideways too. Rails can snap card by card and, with a mouse, show round prev / next buttons on the sides with more content; they fade out at the ends, and one that hides under keyboard focus hands focus to the rail.

## Installation

```bash
npx wingo-ui@latest add scroll-area
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/scroll-area` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/ui/scroll-area.tsx`

npm dependencies: `npm i @radix-ui/react-scroll-area lucide-react`

Wingo UI dependencies (installed automatically):

- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))
- [useMediaQuery](https://wingo-ui.com/components/use-media-query) ([Markdown](https://wingo-ui.com/components/use-media-query.md))
- [useScrollEdges](https://wingo-ui.com/components/use-scroll-edges) ([Markdown](https://wingo-ui.com/components/use-scroll-edges.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
import { ScrollArea } from "@/components/ui/scroll-area"

const listings = [
  { id: "1", title: "Cube Aim bike", city: "Brooklyn", price: "$450" },
  { id: "2", title: "iPhone 13, 128 GB", city: "Boston", price: "$460" },
  { id: "3", title: "Sofa bed", city: "Portland", price: "$280" },
]

export function Notifications({ items }: { items: { id: string; title: string; time: string }[] }) {
  return (
    <ScrollArea label="Notifications" maxHeight={320} className="rounded-2xl bg-surface shadow-raised">
      <ul className="divide-y divide-foreground/10">
        {items.map((item) => (
          <li key={item.id} className="flex min-h-14 flex-col justify-center px-4 py-2.5">
            <span className="text-[15px] font-medium">{item.title}</span>
            <span className="text-[13px] text-muted-foreground">{item.time}</span>
          </li>
        ))}
      </ul>
    </ScrollArea>
  )
}

export function Recommended() {
  return (
    <ScrollArea
      orientation="horizontal"
      snap="mandatory"
      controls
      label="Recommended"
      classNames={{ viewport: "scroll-px-4" }}
    >
      <div className="flex w-max gap-3 px-4 pb-3">
        {listings.map((listing) => (
          <article key={listing.id} className="w-56 shrink-0 snap-start rounded-2xl bg-surface p-4 shadow-raised">
            <h3 className="truncate text-sm font-medium">{listing.title}</h3>
            <p className="text-xs text-muted-foreground">{listing.city}</p>
            <p className="mt-2 text-[15px] font-semibold tabular-nums">{listing.price}</p>
          </article>
        ))}
      </div>
    </ScrollArea>
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `orientation?` | `"vertical" \| "horizontal" \| "both"` | `"vertical"` | Which scrollbars render and which edges fade. horizontal turns the area into a rail (the demo swaps the chat for listing cards), both scrolls a wide table either way. |
| `type?` | `"hover" \| "scroll" \| "auto" \| "always"` | `"hover"` | When the bars show: while the pointer is over the area, while it scrolls, whenever the content overflows, or always. On touch screens "hover" behaves like "scroll", since a finger never hovers. Content that fits shows no bars in any mode. |
| `scrollHideDelay?` | `number` | `600` | Milliseconds before the bars fade out after the pointer leaves ("hover") or scrolling stops ("scroll"). |
| `fade?` | `boolean` | `true` | Fades the content out toward every edge with more to scroll. It is a mask, not an overlay, so it works on any surface in both themes and never covers the bars. |
| `fadeSize?` | `number` | `24` | The largest fade in px. Each fade grows with the distance to its edge, so it eases out as you reach the end instead of popping. |
| `scrollbarSize?` | `"sm" \| "md"` | `"md"` | Bar thickness, 8 or 10px. The thumb fills it inside a 2px inset (4 or 6px). On touch screens the bars are indicators only, so a swipe that lands on the thumb still scrolls the content. |
| `snap?` | `"none" \| "mandatory" \| "proximity"` | `"none"` | CSS scroll snap along the orientation. mandatory always rests on an item (card rails), proximity only near one (chips, long lists). The children set snap-start or snap-center. |
| `controls?` | `boolean` | `false` | Horizontal and both: floating prev / next buttons on the sides with more content. Mouse and trackpad only (a finger swipes); they fade out at each end and leave the tab order while hidden. |
| `scrollStep?` | `number` | `0.8` | Share of the viewport width one control click, or Page Up / Down or Space on a focused rail, scrolls. Smooth, and instant under reduced motion. |
| `label?` | `string` | `""` | Accessible name of the scrollable region, like "Messages". With a label the viewport is announced as a region. The demo uses a sample while this is empty. |
| `focusable?` | `boolean` | `true` | Makes the viewport a tab stop while it overflows, so keyboard users can scroll it; the focus ring sits just inside the edge and the bars show while it has focus. Turn it off when the content is itself focusable (a list of links). |
| `maxHeight?` | `number \| string` | `""` | Convenience max height of the viewport: a number (or numeric string) in px, or any CSS length like "50dvh". The area grows with its content up to it. Otherwise size the root with a class (h-72, flex-1 min-h-0). The demo caps the chat with it. |
| `viewportRef?` | `Ref<HTMLDivElement> (a useRef object or an (el) => void callback)` |  | The scrolling element, for scrollTo, scrollIntoView or measuring. The demo uses it to start the chat at the latest message. |
| `controlLabels?` | `{ previous?: string; next?: string }` | `"{ previous: \"Scroll back\", next: \"Scroll forward\" }"` | aria-labels of the prev / next buttons. |
| `onEdgesChange?` | `(edges: { top: boolean; bottom: boolean; left: boolean; right: boolean }) => void` |  | Which edges have more content, called when one flips (never per scroll frame). Handy for a header hairline that appears once the list scrolls under it. |
| `classNames?` | `Partial<Record<"viewport" \| "scrollbar" \| "thumb" \| "controls", string>>` |  | Extra classes for the inner parts. viewport takes scroll padding (scroll-px-4) for rails, thumb a custom color (bg-brand/50), controls a different vertical position. |
| `className?` | `string` |  | Extra classes for the root, merged last: its size (h-72, flex-1 min-h-0) and radius, which the viewport inherits. Other props (style, aria-*, dir) go to the Radix root too; dir="rtl" puts the vertical bar on the left and mirrors the fades, the controls and the Home / End keys of a rail. |
| `children?` | `ReactNode` |  | The content. Rails lay their items out in a w-max flex row of snap-start items. The file also exports ScrollBar (orientation, size "sm" \| "md", classNames.thumb), the same bar as a part for compositions on the Radix root. |

## Parts

### ScrollBar (component)

One slim scrollbar with its thumb, for compositions built on the Radix scroll area root. ScrollArea renders these for you.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `orientation?` | `"vertical" \| "horizontal"` | `"vertical"` | Which axis the bar scrolls. |
| `size?` | `"sm" \| "md"` | `"md"` | 8px (sm) or 10px (md) thick. |
| `classNames?` | `Partial<Record<"thumb", string>>` |  | Extra classes for the thumb, e.g. a custom color. |

### ScrollAreaProps (type)

The props of ScrollArea, for wrappers like a horizontal rail or a chat log.

## Examples

- **Vertical list with fades**: maxHeight caps it; onEdgesChange draws the header hairline once the list scrolls under it.
- **Listing rail**: orientation="horizontal", snap="mandatory" and controls: cards settle one by one, the arrows hide at each end.
- **Both axes**: A 12-column price table in a small box; the fades follow all four edges.
- **Always vs on hover**: type="always" keeps the bar; "hover" shows it while you point at the list, while it has keyboard focus, and while scrolling on touch.
- **Chips on a phone**: snap="proximity" rests near a chip without forcing it; no controls on touch, a swipe is enough.
- **Inside a popover**: maxHeight, label="Results", a slim bar, and focusable={false} since every row is a button already.
- **Slim bar, brand thumb**: scrollbarSize="sm", a longer fade and classNames.thumb in the brand color.

## See also

- [useScrollEdges](https://wingo-ui.com/components/use-scroll-edges) ([Markdown](https://wingo-ui.com/components/use-scroll-edges.md))
- [Scroll Progress](https://wingo-ui.com/components/scroll-progress) ([Markdown](https://wingo-ui.com/components/scroll-progress.md))
- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))

---

Canonical: https://wingo-ui.com/components/scroll-area
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
