# Scroll Area

> Scroll Area: anatomy, keyboard behavior, state, and theming in Askr.

Source: [https://askrjs.com/docs/components/scroll-area](https://askrjs.com/docs/components/scroll-area)

Status: stable. Packages: @askrjs/ui/scroll-area, @askrjs/themes/scroll-area.

**Published packages are authoritative.** Examples may lag behind a published contract. When guidance differs, verify the exports and TypeScript declarations in your installed package, then file an issue.

## Example

```tsx
import { For } from '@askrjs/askr';
import { ScrollArea, ScrollAreaScrollbar, ScrollAreaThumb, ScrollAreaViewport } from '@askrjs/themes/components';

<ScrollArea>
  <ScrollAreaViewport class="activity-log">
    <For each={events} by={(event) => event.id}>
      {(event) => <ActivityRow event={event} />}
    </For>
  </ScrollAreaViewport>
  <ScrollAreaScrollbar orientation="vertical"><ScrollAreaThumb /></ScrollAreaScrollbar>
</ScrollArea>
```

## Published props

Generated from the TypeScript declarations shipped by the installed package.

### `ScrollAreaAsChildProps`

Import from `@askrjs/ui/scroll-area`.

- `asChild: true;`
- `children: JSX.Element;`
- `id?: string | undefined;`

### `ScrollAreaCornerProps`

Import from `@askrjs/ui/scroll-area`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `ref?: Ref<HTMLDivElement>;`

### `ScrollAreaOwnProps`

Import from `@askrjs/ui/scroll-area`.

- `children?: unknown;`
- `id?: string | undefined;`

### `ScrollAreaProps`

Import from `@askrjs/ui/scroll-area`.

- `children?: unknown;`
- `id?: string | undefined;`

### `ScrollAreaScrollbarProps`

Import from `@askrjs/ui/scroll-area`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `orientation?: "vertical" | "horizontal" | undefined;`
- `ref?: Ref<HTMLDivElement>;`

### `ScrollAreaThumbProps`

Import from `@askrjs/ui/scroll-area`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `ref?: Ref<HTMLDivElement>;`

### `ScrollAreaViewportAsChildProps`

Import from `@askrjs/ui/scroll-area`.

- `asChild: true;`
- `children: JSXElement;`
- `ref?: Ref<Element>;`

### `ScrollAreaViewportProps`

Import from `@askrjs/ui/scroll-area`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `ref?: Ref<HTMLDivElement>;`

## Purpose

Scroll Area gives you a scroll container whose scrollbars you can style, without giving up native scrolling, keyboard paging, or momentum on touch. Compose `ScrollArea` around a `ScrollAreaViewport` holding the content, plus a `ScrollAreaScrollbar` and `ScrollAreaThumb` per axis and a `ScrollAreaCorner` when both axes are present. Reach for it when a default scrollbar would break the visual design — not to hide that content overflows, which users still need to be able to discover.

## Install and import

Import ScrollArea, ScrollAreaViewport, ScrollAreaScrollbar, ScrollAreaThumb, and ScrollAreaCorner from @askrjs/ui/scroll-area, or the same five names pre-styled from @askrjs/themes/scroll-area, which re-exports the ui components as-is and depends entirely on CSS for appearance. Load @askrjs/themes/default for the actual visuals — the components alone only supply structure and shared IDs, no styling.

## Live examples

A typical instance is ScrollArea wrapping a ScrollAreaViewport that holds your scrollable content, alongside a ScrollAreaScrollbar containing a ScrollAreaThumb, with an optional ScrollAreaCorner when both axes scroll. It's the shape you'd reach for around a tall settings panel, a chat sidebar, or any fixed-height region where you want scrolling to feel consistent with the theme's tokens rather than the raw OS scrollbar.

## Anatomy

ScrollArea itself renders no DOM — it's a context provider that generates and shares related IDs (viewport, scrollbar, thumb, corner) across its children, and throws if any part is used outside a ScrollArea. ScrollAreaViewport renders the actual overflow: auto element that does the real scrolling; ScrollAreaScrollbar, ScrollAreaThumb, and ScrollAreaCorner are separate divs positioned by CSS, not children of the viewport itself.

## State model

ScrollAreaViewport is ultimately just a plain scrollable div, so scrollTop and scrollLeft sit in the DOM the same way they would for any natively-scrolled element for the purposes of reading/setting position yourself — attach a ref and use the standard DOM scroll APIs directly if you need to drive it from code (there's no `apiRef` or imperative handle the way Virtual List has one). That said, the component does actively track derived scroll state internally: percentage-scrolled and overflow are recomputed on every scroll and resize (via `ResizeObserver`) and pushed onto the scrollbar's `aria-valuenow`/`data-state` attributes — it's not purely passive.

## Keyboard and accessibility

ScrollAreaViewport is a genuinely scrollable element (`role="region"`, `tabIndex={0}`), so native wheel scrolling and focus-then-arrow-key behavior work there too. But `ScrollAreaScrollbar` is not just a decorative accent alongside it — it renders with `role="scrollbar"`, `aria-controls` pointing at the viewport, `aria-orientation`, and `aria-valuemin`/`aria-valuemax`/`aria-valuenow` kept in sync via a `ResizeObserver` and scroll listener, and it has its own `onKeyDown` handling Arrow/Page Up/Page Down/Home/End that calls back into the viewport's actual `scrollTop`/`scrollLeft` — a real, working custom scrollbar widget, not an `aria-hidden` decoration. It only becomes focusable (`tabIndex={0}`) when the content actually overflows.

## Styling and tokens

The default theme styles the viewport's native scrollbar directly too — [data-slot="scroll-area-viewport"] gets scrollbar-width: thin and scrollbar-color set to --ak-color-border-strong — so you get a themed native scrollbar even before considering the custom one. `ScrollAreaScrollbar` and `ScrollAreaThumb` get track/thumb styling (width, radius, background) from the default CSS, and unlike a purely cosmetic overlay, they *are* synced to the viewport's actual scroll position and size at the JS layer (percentage, overflow, and visibility all update live) — the component ships as a real functioning scrollbar, keyboard-operable and all, not just a static visual accent.

## API

ScrollAreaProps takes just children and an optional id, used to derive the shared part IDs. ScrollAreaViewportProps, ScrollAreaScrollbarProps (which adds orientation?: 'vertical' | 'horizontal'), ScrollAreaThumbProps, and ScrollAreaCornerProps each extend the native div's props, so every part accepts ordinary HTML attributes plus asChild where the type allows it.

## Edge cases

Every part throws if rendered outside a ScrollArea, since they all read shared IDs from context — you can't reuse ScrollAreaViewport or ScrollAreaThumb standalone. ScrollAreaScrollbar and ScrollAreaThumb *are* functionally linked to the viewport's scroll position — a `ResizeObserver` plus a scroll listener keep `aria-valuenow` and a `data-state` (`visible`/`hidden`) attribute in sync on every scroll and resize — so it's safe to rely on them as a real scroll indicator, not just a decorative accent.

## Related pages

See Virtual List and Virtual Table if what you actually need is windowed rendering for a large dataset rather than just a styled scroll region. See Table for content commonly placed inside a Scroll Area when it's wider or taller than its container.

## Documentation navigation

[Previous](https://askrjs.com/docs/components/virtual-table/index.md) | [Next](https://askrjs.com/docs/components/card/index.md)
