Scroll Area
Scroll Area: anatomy, keyboard behavior, state, and theming in Askr.
Example
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. Named types in the Type column define the accepted values.
ScrollAreaAsChildProps
Import from @askrjs/ui/scroll-area.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSX.Element; | — | — |
id | id?: string | undefined; | — | — |
ScrollAreaCornerProps
Import from @askrjs/ui/scroll-area.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
ScrollAreaOwnProps
Import from @askrjs/ui/scroll-area.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
id | id?: string | undefined; | — | — |
ScrollAreaProps
Import from @askrjs/ui/scroll-area.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
id | id?: string | undefined; | — | — |
ScrollAreaScrollbarProps
Import from @askrjs/ui/scroll-area.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
orientation | orientation?: "vertical" | "horizontal" | undefined; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
ScrollAreaThumbProps
Import from @askrjs/ui/scroll-area.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
ScrollAreaViewportAsChildProps
Import from @askrjs/ui/scroll-area.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
ref | ref?: Ref<Element>; | — | — |
ScrollAreaViewportProps
Import from @askrjs/ui/scroll-area.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
ref | 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.