---
title: Scroll Area
description: "Native scrolling for Astro panels, with custom tracks, keyboard access, and optional edge fades."
sidebar:
    badge: new
seo:
    title: "Scroll Area for Astro — examples and usage"
---

```astro
---
import { ScrollArea } from "@gnosticai/ui/ui/scroll-area"
---

<ScrollArea
  scrollFade
  aria-label="Project files"
  class="max-h-64 w-72 rounded-lg border"
>
  <ul class="divide-border divide-y px-4">
    {Array.from({ length: 16 }, (_, index) => (
      <li class="py-3 text-sm">Project file {index + 1}</li>
    ))}
  </ul>
</ScrollArea>
```

## Installation

```bash
bunx --bun shadcn@latest add @gnosticai/scroll-area
```

## Usage

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

<ScrollArea aria-label="Project files" class="max-h-64 rounded-md border">
  <div class="p-4">Your content</div>
</ScrollArea>
```

The component uses 6px tracks, rounded thumbs at 20% foreground opacity,
and scrollbars that appear
on hover, keyboard focus, or scrolling. The focus ring uses the current theme.

Astro renders the component parts. A small client script measures overflow and
handles thumb dragging and track clicks. The viewport uses native wheel, touch,
and keyboard scrolling. Browser scrollbars remain available when JavaScript is off.

## Component parts

`ScrollArea` assembles the parts. Use `ScrollAreaRoot` when you need direct control:

```astro
---
import {
  ScrollAreaRoot,
  ScrollAreaViewport,
  ScrollAreaContent,
  ScrollAreaScrollbar,
  ScrollAreaThumb,
  ScrollAreaCorner,
} from "@/components/ui/scroll-area"
---

<ScrollAreaRoot orientation="both" class="h-64 w-80 rounded-md border">
  <ScrollAreaViewport aria-label="Project files">
    <ScrollAreaContent class="p-4">Your content</ScrollAreaContent>
  </ScrollAreaViewport>
  <ScrollAreaScrollbar><ScrollAreaThumb /></ScrollAreaScrollbar>
  <ScrollAreaScrollbar orientation="horizontal"><ScrollAreaThumb /></ScrollAreaScrollbar>
  <ScrollAreaCorner />
</ScrollAreaRoot>
```

Keep the viewport, scrollbars, and corner as direct children of the root.
Keep the content as a direct child of the viewport. Each scrollbar contains one
thumb. `ScrollAreaScrollbar` supplies a thumb when its default slot is empty.
`ScrollBar` is an alias for `ScrollAreaScrollbar`.

```astro
---
import {
  ScrollAreaRoot,
  ScrollAreaViewport,
  ScrollAreaContent,
  ScrollAreaScrollbar,
  ScrollAreaThumb,
  ScrollAreaCorner,
} from "@gnosticai/ui/ui/scroll-area"
---

<ScrollAreaRoot orientation="both" class="h-56 w-80 rounded-md border">
  <ScrollAreaViewport aria-label="Custom scroll area">
    <ScrollAreaContent class="p-4">
      <div class="flex w-max flex-col gap-3 text-sm">
        {Array.from({ length: 16 }, (_, i) => (
          <p>Row {i + 1}: This longer line can scroll in both directions.</p>
        ))}
      </div>
    </ScrollAreaContent>
  </ScrollAreaViewport>
  <ScrollAreaScrollbar>
    <ScrollAreaThumb class="bg-primary/40" />
  </ScrollAreaScrollbar>
  <ScrollAreaScrollbar orientation="horizontal">
    <ScrollAreaThumb class="bg-primary/40" />
  </ScrollAreaScrollbar>
  <ScrollAreaCorner />
</ScrollAreaRoot>
```

## Examples

### Scroll fade

Set `scrollFade` to fade an edge only when there is more content beyond it.
Change `--scroll-area-fade-size` to adjust the fade length. Its default is `1.5rem`.

```astro
---
import { ScrollArea } from "@gnosticai/ui/ui/scroll-area"
---

<ScrollArea
  scrollFade
  class="max-h-64 w-72 rounded-md border"
  aria-label="Activity"
>
  <ul class="flex flex-col gap-4 p-4 text-sm">
    {Array.from({ length: 16 }, (_, i) => (
      <li>Activity {i + 1}: The project is ready for review.</li>
    ))}
  </ul>
</ScrollArea>
```

### Horizontal scrolling

Use `orientation="horizontal"` with content wider than the viewport.

```astro
---
import { ScrollArea } from "@gnosticai/ui/ui/scroll-area"
---

<ScrollArea
  orientation="horizontal"
  aria-label="Project stages"
  class="w-80 rounded-lg border"
>
  <ol class="flex w-max gap-4 p-4">
    {["Plan", "Design", "Build", "Review", "Release"].map((stage, index) => (
      <li class="bg-muted flex w-36 shrink-0 flex-col gap-2 rounded-md p-4">
        <span class="text-muted-foreground text-sm">Step {index + 1}</span>
        <span class="font-medium">{stage}</span>
      </li>
    ))}
  </ol>
</ScrollArea>
```

### Both scrollbars

Use `orientation="both"`. The corner keeps the two tracks apart.

```astro
---
import { ScrollArea } from "@gnosticai/ui/ui/scroll-area"
---

<ScrollArea
  orientation="both"
  scrollbarGutter
  class="h-64 w-80 rounded-md border"
  aria-label="Grid of cells"
>
  <div class="grid w-max grid-cols-8 gap-2 p-4">
    {Array.from({ length: 80 }, (_, i) => (
      <div class="bg-muted flex size-16 items-center justify-center rounded-md text-sm">
        {i + 1}
      </div>
    ))}
  </div>
</ScrollArea>
```

### Scrollbar space and containment

`scrollbarGutter` reserves space inside the viewport for each enabled axis. The
space remains when content is short, so the content width does not change when
scrollbars appear. `overscrollContain` stops scrolling from passing to a parent
scroller when the viewport reaches an edge. It applies only on overflowing axes.

```astro
---
import { ScrollArea } from "@gnosticai/ui/ui/scroll-area"
---

<ScrollArea
  scrollbarGutter
  overscrollContain
  class="max-h-64 w-72 rounded-md border"
  aria-label="Notifications"
>
  <ul class="flex flex-col gap-4 p-4 text-sm">
    {Array.from({ length: 12 }, (_, i) => (
      <li>Notification {i + 1}: A new file is available.</li>
    ))}
  </ul>
</ScrollArea>
```

### Fill the viewport

Use `fill` for a flex column with a footer at the bottom. Keep its default value
for lists whose height follows their content.

```astro
---
import { ScrollArea } from "@gnosticai/ui/ui/scroll-area"
---

<ScrollArea
  fill
  class="h-64 w-72 rounded-md border"
  aria-label="Project summary"
>
  <div class="flex h-full flex-col gap-4 p-4 text-sm">
    <p>Current project</p>
    <p class="text-muted-foreground">The content fills the viewport.</p>
    <footer class="text-muted-foreground mt-auto">Last updated today</footer>
  </div>
</ScrollArea>
```

## Set the height

Put size classes on `ScrollArea` or `ScrollAreaRoot`. Use `max-h-64` to let short
content keep its natural height and scroll when it exceeds 16rem. Use `h-64`
when the container must always be 16rem high. Put content padding on a child.

### Inherit a maximum height

The root uses `max-height: inherit` by default. This works when its direct parent
has a maximum height such as `max-h-48`. The viewport fits inside the root.

```astro
---
import { ScrollArea } from "@gnosticai/ui/ui/scroll-area"
---

<div class="max-h-48 w-72 rounded-lg border">
  <ScrollArea aria-label="Recent updates">
    <ul class="flex flex-col gap-3 p-4 text-sm">
      {Array.from({ length: 12 }, (_, index) => (
        <li>Update {index + 1}: Content is ready for review.</li>
      ))}
    </ul>
  </ScrollArea>
</div>
```

`inherit` copies the parent's computed `max-height`. It does not copy `height`,
search other ancestors, or calculate space below a header. If the parent has no
maximum height, the inherited value is `none`. A percentage still needs a definite
containing block height. Parent padding and borders can reduce the available space.
Use `max-h-none` to remove an inherited limit.

See [MDN's max-height reference](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/max-height).

### Use the remaining panel space

Give the parent a height and `flex flex-col`. Set `flex-1` on `ScrollArea` to use
space below a header. The root and viewport have `min-h-0`, so they can shrink
below their content height. Intermediate flex or grid items can also need `min-h-0`.

```astro
---
import { ScrollArea } from "@gnosticai/ui/ui/scroll-area"
---

<section class="flex h-64 w-72 flex-col rounded-lg border">
  <h3 id="scroll-area-tasks" class="shrink-0 border-b p-4 font-medium">
    Tasks
  </h3>
  <ScrollArea aria-labelledby="scroll-area-tasks" class="flex-1">
    <ul class="divide-border divide-y px-4">
      {Array.from({ length: 16 }, (_, index) => (
        <li class="py-3 text-sm">Review task {index + 1}</li>
      ))}
    </ul>
  </ScrollArea>
</section>
```

## Keyboard access

Give `ScrollArea` an `aria-label` or `aria-labelledby`. These attributes, `role`,
`aria-describedby`, and `tabindex` pass to the viewport. In a manual composition,
put them on `ScrollAreaViewport`. A named viewport has `role="region"` by default.
Focus the viewport to use native arrow keys, Page Up, and Page Down. Custom tracks
are hidden from assistive technology; the viewport supplies keyboard access.

## API Reference

`ScrollArea` and `ScrollAreaRoot` accept these options:

<AutoTypeTable
	path="../../packages/ui/src/components/ui/scroll-area/types.ts"
	name="ScrollAreaOptions"
/>

`ScrollArea` also accepts `viewportProps` for native viewport attributes and classes.
Other native attributes and `class` apply to the root. Each part accepts native
div attributes and a default slot, except the empty corner. The root renders a
custom HTML element. All parts use Astro `class` and slots.

`ScrollAreaScrollbar` accepts `orientation="vertical"` (default) or `"horizontal"`.
`ScrollAreaViewport` has `tabindex="0"` by default. The content wrapper uses
`min-width: 0` unless `clampContentMinWidth={false}` is set. Horizontal content
should declare its width, for example with `w-max`.

Read the [installed source](/r/scroll-area.json) for the component source.

## When to use Scroll Area

Use a Scroll Area when a bounded list or panel must scroll independently of the page. Set its height or maximum height and put padding inside its content. Without a size limit, the container can grow with the content and has no reason to scroll. For horizontal content, give the child a width such as `w-max`.

## Keyboard and behavior

The custom element keeps a native scroll viewport. Wheel, touch, and keyboard scrolling use the browser. Its script measures overflow and moves the custom thumbs; it does not replace the viewport with a simulated scroller. Give the viewport a useful name and keep its focus outline visible. With scripts off, the browser scrollbar remains available.

## Theme and layout

Track and thumb colors follow the foreground token. Check contrast over tinted surfaces and check both axes after a theme change. `scrollFade` is an optional edge mask, not a loading signal. `overscrollContain` is useful in a fixed panel but can change how a user reaches the page beneath it. Content is not virtualized, so large lists still create all their HTML nodes.

## Related choices

Use [Table](/components/table) for tabular data and [Dialog](/components/dialog) for a modal task. Keep one clear scroll region in each panel when possible. For most prose pages, ordinary page scrolling is easier to use.
