Scroll Area
Scroll Area: anatomy, keyboard behavior, state, and theming in Askr.
Example
import { For } from '@askrjs/askr/control';
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/themes/scroll-area.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSX.Element; | — | — |
id | id?: string | undefined; | — | — |
ScrollAreaCornerProps
Import from @askrjs/themes/scroll-area.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
ScrollAreaOwnProps
Import from @askrjs/themes/scroll-area.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
id | id?: string | undefined; | — | — |
ScrollAreaProps
Import from @askrjs/themes/scroll-area.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
id | id?: string | undefined; | — | — |
ScrollAreaScrollbarProps
Import from @askrjs/themes/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/themes/scroll-area.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
ScrollAreaViewportAsChildProps
Import from @askrjs/themes/scroll-area.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
ref | ref?: Ref<Element>; | — | — |
ScrollAreaViewportProps
Import from @askrjs/themes/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.