Navigation@knitui/components
ScrollArea
ScrollArea is a scrollable viewport with custom overlay scrollbars that render and behave identically on web and React Native — the browser-native scrollbar is hidden and replaced by draggable thumbs we draw ourselves, so they are fully themeable. It mirrors Mantine's ScrollArea public API (scrollbars, type, scrollbarSize, offsetScrollbars, scrollHideDelay, onScrollPositionChange, viewportRef, viewportProps) and adds scrollbarProps / thumbProps / cornerProps styling hooks. Ships a ScrollArea.Autosize sub-component that grows with content up to a maximum height before scrolling.
import { ScrollArea } from "@knitui/components";Playground
Loading playground…
<ScrollArea
scrollbars="xy"
type="hover"
scrollbarSize={12}
offsetScrollbars={false}
scrollHideDelay={1000}
width={320}
height={200}
/>Examples
Vertical Only
Vertical-only scrolling — horizontal overflow is hidden.
Loading example…
Horizontal Only
Horizontal-only scrolling — a wide single-line row of items.
Loading example…
Both Axes
Both axes active — a wide, tall content area that scrolls in every direction.
Loading example…
Shadows
Elevation shadow ladder applied to the ScrollArea frame via the inherited shadow prop. Distinct from EdgeShadows, which is the scroll-fade overlay.
Loading example…
Scrollbar Sizes
Thick scrollbar (16 px) vs. thin scrollbar (4 px) side by side.
Loading example…
Scroll Position Tracking
onScrollPositionChange — the current x/y offset is displayed in real time.
Loading example…
Autosize
ScrollArea.Autosize grows with its content until maxHeight is reached, then scrolls.
Loading example…
Custom Scrollbars
Fully custom scrollbars — the rail, thumb and corner are styled via scrollbarProps, thumbProps and cornerProps. These accept any Box prop, so colour, radius, padding and even hover/press styles are yours to set.
Loading example…
Visibility Types
type behaviours side by side — always (persistent), hover (on hover/scroll), scroll (while scrolling only) and never (no scrollbars, still scrolls).
Loading example…
Imperative Controls
Imperative handle — the forwarded ref is a ScrollAreaHandle exposing scrollTo, scrollToTop, scrollToBottom, scrollToEnd, scrollIntoView, getViewport and getScrollPosition.
Loading example…
Edge Shadows
Edge fade shadows — overlays fade in on each scrollable edge to hint at hidden content, and fade out as you reach that edge. Driven by the shadows prop.
Loading example…
Infinite Scroll
Infinite scroll — onReachBottom (with a reachThreshold) appends more items as you approach the end. onReachTop/onReachStart/onReachEnd/onScrollEnd round out the edge callbacks.
Loading example…
Stick To Bottom
Stick-to-bottom — a chat/log pattern. New lines pin to the bottom while you're already there; scroll up and the follow releases until you return.
Loading example…
Track Paging
Track click paging — with trackClickBehavior="page", clicking the rail outside the thumb steps one viewport toward the click instead of jumping.
Loading example…
Hover Grow
Hover-grow thumb — the rail rests thin (idleScrollbarSize) and animates to scrollbarSize on hover/drag, macOS-style. Hover the area to see it thicken.
Loading example…
Keyboard
Keyboard scrolling — focus the area (Tab) and use Arrow / PageUp / PageDown / Home / End / Space to scroll. Enabled by default via keyboardScrolling.
Loading example…
Styles
Per-slot styles targets individual parts — here the viewport, scrollbar, thumb, and corner.
Loading example…
Props
| Prop | Type | Default | Description |
|---|---|---|---|
cornerProps | Partial<GetFinalProps<RNTamaguiViewNonStyleProps, StackStyleBase, { shadow?: "xs" | "sm" | "md" | "lg" | "xl" | "xxs" | "xxl" | undefined; }>> | undefined | — | Props spread onto the corner shown where the two scrollbars meet. |
disabled system | boolean | undefined | — | |
handleRef | Ref<ScrollAreaHandle> | undefined | — | Imperative handle for programmatic scrolling. An alternative to the forwarded `ref` (which is also the {@link ScrollAreaHandle}); useful when the `ref` is needed elsewhere. |
idleScrollbarSize | number | undefined | 6 | Idle scrollbar thickness (px). The rail rests this thin and animates up to `scrollbarSize` on hover/drag (macOS-style overlay). Set equal to `scrollbarSize` to disable the grow. |
keyboardScrolling | boolean | undefined | true | Make the viewport focusable and scroll it with Arrow / Page / Home / End / Space keys. Web only — a no-op on native. |
keyboardShouldPersistTaps | boolean | "always" | "never" | "handled" | undefined | RN default (`"never"`) | How taps are delivered while the on-screen keyboard is up (native only — maps to the React Native `ScrollView` prop of the same name; ignored on web). The RN default (`"never"`) swallows the FIRST tap to dismiss the keyboard, so a tap on a pressable child (e.g. a dropdown option in a focus-driven combobox) is eaten — and dismissing the keyboard blurs the field, closing the dropdown before the option's press can land. `"handled"` keeps the keyboard up when a child handles the tap, so the option is selected normally while a tap on empty space still dismisses. |
keyStep | number | undefined | 40 | Pixels scrolled per arrow-key press. |
offsetScrollbars | boolean | "x" | "y" | "present" | undefined | false | Reserve stable gutter space so content does not sit under the overlay scrollbar. |
onReachBottom | (() => void) | undefined | — | Fired once when the viewport reaches the bottom. |
onReachEnd | (() => void) | undefined | — | Fired once when the viewport reaches the far right (horizontal end). |
onReachStart | (() => void) | undefined | — | Fired once when the viewport reaches the far left (horizontal start). |
onReachTop | (() => void) | undefined | — | Fired once when the viewport reaches the top. |
onScrollEnd | ((position: ScrollPosition) => void) | undefined | — | Fired after scrolling settles, with the resting `{ x, y }` offset. |
onScrollPositionChange | ((position: ScrollPosition) => void) | undefined | — | Called with the `{ x, y }` scroll offset whenever the viewport scrolls. |
reachThreshold | number | undefined | 0 | Distance from an edge (px) at which the reach callbacks fire. |
renderShadow | ((edge: Edge, size: number) => ReactNode) | undefined | — | Custom edge-overlay renderer — an escape hatch for true native gradients (e.g. `expo-linear-gradient`). Called per visible edge; return `null` to fall back to the default fade. |
scrollbarProps | Partial<GetFinalProps<RNTamaguiViewNonStyleProps, StackStyleBase, { shadow?: "xs" | "sm" | "md" | "lg" | "xl" | "xxs" | "xxl" | undefined; }>> | undefined | — | Props spread onto each scrollbar track (style the rail). |
scrollbars | ScrollAreaScrollbars | undefinedx · y · xy · false | "xy" | Axes on which scrolling is enabled. |
scrollbarSize | number | undefined | 12 | Scrollbar (thumb + rail) thickness in px. |
scrollHideDelay | number | undefined | 1000 | Delay in ms before scrollbars hide in `hover` / `scroll` modes. |
scrollValueX | SharedValue<number> | undefined | — | Horizontal counterpart of {@link scrollValueY}. |
scrollValueY | SharedValue<number> | undefined | — | Reanimated shared value the live vertical scroll offset is mirrored INTO on the UI thread every frame, with NO JS-thread round-trip. Pass this when a reanimated animation (e.g. a parallax / collapsing header) needs the live offset: create it with `useSharedValue(0)` and feed the SAME value to your `useAnimatedStyle`. Prefer this over `onScrollPositionChange` for animation — that callback hops to JS via `runOnJS`, so the animation stutters whenever the JS thread is busy. Native only; the web ScrollArea ignores it (drive web from `onScrollPositionChange` + React state instead). |
shadow system | "xs" | "sm" | "md" | "lg" | "xl" | "xxs" | "xxl" | undefined | — | |
shadowColor | string | undefined | — | Fade colour. Web fades from this colour to transparent; native paints a translucent scrim of it. Defaults to the theme background. |
shadows | ShadowEdges | undefined | false | Show fade overlays on scrollable edges to hint at hidden content. |
shadowSize | number | undefined | 32 | Fade depth in px. |
stickToBottom | boolean | undefined | false | Auto-scroll to the bottom as content grows, but only while the user is already near the bottom — released the moment they scroll up. |
stickToBottomThreshold | number | undefined | 24 | Slack (px) within which "at the bottom" counts for sticking. |
styles system | SlotStyles<ScrollAreaStyles> | undefined | — | Uniform per-slot style passthrough. Sugar over the discrete `*Props` above; each slot layers after its matching `*Props`. See {@link ScrollAreaStyles}. |
theme system | ThemeName | null | undefined | — | Applies a theme to this element |
thumbProps | Partial<GetFinalProps<RNTamaguiViewNonStyleProps, StackStyleBase, { shadow?: "xs" | "sm" | "md" | "lg" | "xl" | "xxs" | "xxl" | undefined; }>> | undefined | — | Props spread onto each scrollbar thumb (style the draggable handle). |
trackClickBehavior | "page" | "jump" | undefined | "jump" | What a click on the rail (outside the thumb) does: `jump` centres the thumb on the click; `page` steps one viewport toward it. |
type | ScrollAreaType | undefinedhover · auto · always · scroll · never | "hover" | Scrollbar visibility: `hover` (on hover/scroll), `scroll` (while scrolling), `auto` (whenever content overflows), `always`, or `never`. |
viewportProps | Partial<GetFinalProps<RNTamaguiViewNonStyleProps, StackStyleBase, { shadow?: "xs" | "sm" | "md" | "lg" | "xl" | "xxs" | "xxl" | undefined; }>> | undefined | — | Props spread onto the scrollable viewport. |
viewportRef | Ref<TamaguiElement> | undefined | — | Ref assigned to the scrollable viewport element. |
Style slots
Every part of ScrollArea can be styled through the styles prop. Explicit props on the component always win over slot styles.
| Slot | Usage |
|---|---|
corner | styles={{ corner: { … } }} |
scrollbar | styles={{ scrollbar: { … } }} |
thumb | styles={{ thumb: { … } }} |
viewport | styles={{ viewport: { … } }} |
Accessibility & DOM props (2)
| Prop | Type |
|---|---|
key | Key | null | undefined |
ref | Ref<ScrollAreaHandle> | undefined |
Plus 497 inherited style props from Box — the full Tamagui/React Native style surface, including token shorthands like p, mx, bg and c. See Tokens for the scales they accept, or the full list.