JGuang

ScrollableArea

ScrollableArea · Astryx

Preview
npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0

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.

PropTypeDefaultDescription
axis'inline' | 'block' | 'both''block'Logical axis or axes where native scrolling is allowed.
label *stringAccessible 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.
widthSizeValueWidth of the viewport; a number is interpreted as pixels, a string is used as-is.
heightSizeValueHeight of the viewport; a number is interpreted as pixels, a string is used as-is.
maxWidthSizeValueMaximum width of the viewport.
minHeightSizeValueMinimum 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.
padding0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 100Content padding on every logical edge; publishes matching inset geometry.
paddingInline0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10Logical inline-axis content padding; overrides padding on that axis.
paddingInlineStart0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10Logical inline-start content padding; overrides broader padding values.
paddingInlineEnd0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10Logical inline-end content padding; overrides broader padding values.
paddingBlock0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10Logical block-axis content padding; overrides padding on that axis.
paddingBlockStart0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10Logical block-start content padding; overrides broader padding values.
paddingBlockEnd0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10Logical block-end content padding; overrides broader padding values.
isFullBleedbooleanfalseLets the viewport escape inherited container padding without changing content padding.
childrenReactNodeContent rendered inside the observed content box.
xstyleStyleXStylesStyleX 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, or both from 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 useScrollableArea instead 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 isFullBleed only 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-area theme 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.