ScrollableArea
Provides a native scroll viewport and a real observed content box. The viewport enters the tab order only while a requested logical axis is effectively scrollable, and containment applies only to effective axes.
Installation
npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0
Requires React 19+, base CSS and a Theme provider. See Setup.
Scrollable Area
Provides a native scroll viewport and a real observed content box. The viewport enters the tab order only while a requested logical axis is effectively scrollable, and containment applies only to effective axes.
| Prop | Type | Default | Description |
|---|---|---|---|
| axis | 'inline' | 'block' | 'both' | 'block' | Logical axis or axes where native scrolling is allowed. |
| label * | string | Accessible name for the viewport when it becomes keyboard scrollable. | |
| role | 'group' | 'region' | 'group' | Semantics for the named viewport. |
| overscroll | 'allow' | 'contain' | 'allow' | Whether effective axes continue scrolling an ancestor at their edge. |
| width | SizeValue | Width of the viewport; a number is interpreted as pixels, a string is used as-is. | |
| height | SizeValue | Height of the viewport; a number is interpreted as pixels, a string is used as-is. | |
| maxWidth | SizeValue | Maximum width of the viewport. | |
| minHeight | SizeValue | Minimum height of the viewport. | |
| stickyContainment | 'whenScrollable' | 'always' | 'whenScrollable' | Whether fitting content passes Sticky ownership to an outer container or deliberately keeps this viewport as the CSS Sticky boundary. |
| padding | 0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 0 | Content padding on every logical edge; publishes matching inset geometry. |
| paddingInline | 0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | Logical inline-axis content padding; overrides padding on that axis. | |
| paddingInlineStart | 0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | Logical inline-start content padding; overrides broader padding values. | |
| paddingInlineEnd | 0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | Logical inline-end content padding; overrides broader padding values. | |
| paddingBlock | 0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | Logical block-axis content padding; overrides padding on that axis. | |
| paddingBlockStart | 0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | Logical block-start content padding; overrides broader padding values. | |
| paddingBlockEnd | 0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | Logical block-end content padding; overrides broader padding values. | |
| isFullBleed | boolean | false | Lets the viewport escape inherited container padding without changing content padding. |
| children | ReactNode | Content rendered inside the observed content box. | |
| xstyle | StyleXStyles | StyleX sizing and native scrollbar presentation overrides for the viewport. |
Usage guidance
- Give every area a concise label that identifies the content keyboard users will scroll.
- Choose
inline,block, orbothfrom content intent; the component maps the logical axes through writing mode and direction. - Keep the default
overscroll="allow"for nested areas unless the interaction deliberately needs containment. - Use
useScrollableAreainstead when a component already owns both a viewport and a suitable content box, or when children must remain direct flex/grid items or retain a definite percentage block-size basis. - ScrollableArea owns one normal block content box with a 100% minimum size. Inline and both-axis modes use max-content inline sizing, so intrinsic inline layout is intentionally wider than the viewport.
- Use logical padding props on the content box so nested full-bleed components receive the same inset geometry.
- Set
isFullBleedonly when the viewport itself should reach an ancestor container edge; it is off by default. - Set
stickyContainment="always"only when a fitting viewport should intentionally remain a Sticky boundary. - Avoid: Hide the native scrollbar without another visible and operable overflow affordance.
- Avoid: Add another overflow wrapper around ScrollableArea; one native viewport should own scrolling.
Theming API
{
"targets": [
{
"className": "astryx-scrollable-area",
"visualProps": [
"axis"
]
}
]
}
Anatomy
- Viewport: The root native scroll container, accessible name owner, focus target while effective, and
astryx-scrollable-areatheme target. - Content box: A real inner layout box observed together with the viewport. For inline scrolling it uses max-content inline sizing with a 100% minimum.
Examples
- Scrollable Area: A fixed-height file panel that scrolls on the block axis; the heading sits inside the viewport, so it scrolls away with the content.
- Scrollable Area — Inline Rail: axis="inline" scrolls the same cards sideways; isFullBleed lets the rail run to the panel edges while the content padding keeps the first card aligned with the heading.
- Scrollable Area — Overscroll: The same section, two edge policies: the default overscroll="allow" hands a gesture that reaches the end to the panel behind it, while overscroll="contain" stops it at the section edge.
- Scrollable Area — Sticky Section Labels: A scrollable viewport is the containing block for position: sticky, so section labels pin to the top edge of the list instead of the page.
- Scrollable Area — Sticky Passthrough: A section whose content fits clips instead of scrolling, so its sticky label passes outward and pins to the panel edge. The next section sets stickyContainment="always" and keeps its label in its own box.
- Scrollable Area — Two-Axis Board: axis="both" gives the content box max-content inline sizing, so a board wider and taller than its panel scrolls on both axes from one viewport.
Source StyleX compilation
ScrollableAreaOverscrollChaining, ScrollableAreaStickyGroupHeaders, ScrollableAreaStickyPassthrough contain stylex.create and require a StyleX source build pipeline. Precompiled Core CSS alone does not compile these examples. See Styling before copying them.