Askr documentation
UI & Components

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.

PropTypeDefaultDescription
asChildasChild: true;
childrenchildren: JSX.Element;
idid?: string | undefined;

ScrollAreaCornerProps

Import from @askrjs/ui/scroll-area.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;
refref?: Ref<HTMLDivElement>;

ScrollAreaOwnProps

Import from @askrjs/ui/scroll-area.

PropTypeDefaultDescription
childrenchildren?: unknown;
idid?: string | undefined;

ScrollAreaProps

Import from @askrjs/ui/scroll-area.

PropTypeDefaultDescription
childrenchildren?: unknown;
idid?: string | undefined;

ScrollAreaScrollbarProps

Import from @askrjs/ui/scroll-area.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;
orientationorientation?: "vertical" | "horizontal" | undefined;
refref?: Ref<HTMLDivElement>;

ScrollAreaThumbProps

Import from @askrjs/ui/scroll-area.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;
refref?: Ref<HTMLDivElement>;

ScrollAreaViewportAsChildProps

Import from @askrjs/ui/scroll-area.

PropTypeDefaultDescription
asChildasChild: true;
childrenchildren: JSXElement;
refref?: Ref<Element>;

ScrollAreaViewportProps

Import from @askrjs/ui/scroll-area.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;
refref?: 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.

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.