Scroll region

A scroll region holds content that scrolls inside a bounded surface and tells the reader where it continues. Choose a divider or a shadow, and the reader sees a line or a soft shadow at each edge the content has scrolled past, while pinned rails keep a header in view. Scroll the tiles below to watch the edges respond.

Moss tea Fern latte Basil soda Mint fizz Oat chai Cocoa Today Stand-up Design review Lunch walk Pairing Demo prep Retro Fixed corners Added regions Tuned springs Fixed a ring Faster paint New themes Cart Sketchbook Gel pens Washi tape Index cards Stickers Total $42 Week 40
Mon Standup Tue Review Wed Demo day Thu Offsite Fri Retro Sat Market Sun Rest
07:10 Oslo 08:45 Porto 09:30 Kyoto 11:05 Lima 12:20 Cairo 13:15 Perth 14:40 Quito
Field guide Color theory Pocket atlas Night sky Tide tables Moss atlas Inbox Ana: lunch? Bo: new build Dara: photos Chidi: notes Eli: invoice Fay: trip Lizard Lounge Slow Hue Camo Disco Tail Spin Moss Hop Dusk Drift Passport Charger Tickets Sunscreen Snacks 5 left

Reference docs⁠

BlazorReactWebOverviewExamplesAPIStylingAccessibility

Built in⁠

  • block-indicator and inline-indicator each mark the edges content has scrolled past with a divider, a shadow, or nothing.
  • Four pinned rails hold a header, a footer, or a gutter in place while the content moves.
  • Once content overflows, the region takes a tab stop, a name from region-label, and keyboard scrolling.
  • scroll-extent-change fires each time a region gains or loses a scrollable axis, or reaches an end of one.
  • The block axis follows your style configuration, so one setting restyles every region and dialog.

Using scroll regions well⁠

  • Pick one block-indicator form per surface: a divider or a shadow under a pinned bar, never both.
  • Reserve dividers for edges where a rail is pinned, and let a shadow mark an edge where content simply continues.
  • Leave inline-indicator at none unless the content overflows sideways.
  • Give the region a bounded height, from a flex parent or a block size, so the content scrolls instead of growing the page.
  • Put the headline and the actions in rails rather than in the scrolling content, so they stay in view.
  • Name every region that can scroll with a short region-label describing its content.

Block indicator⁠

block-indicator sets what marks the top and bottom edges once content has scrolled past them: divider, shadow, or none. Omit it and the region takes the form from your style configuration, which is shadow unless you have changed it. The dialog reads the same setting, so one change restyles both.

A divider needs a rail at the edge it marks, and a shadow does not. A region whose content fits shows no indicator and no scrollbar.

Inline indicator⁠

inline-indicator does the same for the left and right edges in left-to-right text, with the same three forms. It defaults to none and does not read your style configuration, so each region that scrolls sideways opts in on its own.

Pinned rails⁠

Four slots pin content around the scrolling area: block-start, block-end, inline-start, and inline-end. A rail stays put while the content moves, and the two inline rails swap sides in right-to-left text. A divider appears only at an edge whose rail holds content.

Naming the region⁠

region-label takes a plain string, so write the words that name the content, such as "Release notes".

Reading the scroll extent⁠

scroll-extent-change fires when an axis starts or stops scrolling, or reaches either end of its travel. It does not fire for scroll progress. resetScroll() returns both axes to the start, and measureNow() updates the scroll extent without waiting.

Properties⁠

PropertyAttributeTypeDescription
blockIndicatorblock-indicatordivider, none, shadowScrollIndicatorScrollIndicator.Divider, .None, .Shadowdefault ScrollIndicator.Shadow, from the style configurationMarks the block-axis edges when content has scrolled past them. It follows the style configuration, as the scroll-indicator of cw-dialog and cw-side-sheet does, so one configuration restyles all three. See Block indicator.
inlineIndicatorinline-indicatordivider, none, shadowScrollIndicatorScrollIndicator.Divider, .None, .Shadowdefault ScrollIndicator.NoneMarks the inline-axis edges when content has scrolled past them. Unlike blockIndicator, it does not follow the style configuration: the inline axis is opt-in, per region. See Inline indicator.
regionLabelregion-labelstringThe accessible name of the scrolling area while it takes a tab stop. Set it to say what the area holds, such as a dialog's content. Without one, the area is still focusable and scrollable from the keyboard, but a screen reader does not announce it as a named region. Pass text, not an id. See Naming the region.

A divider shows only at an edge that has a pinned rail, while a shadow shows at any edge content has scrolled past. region-label applies only while content overflows.

Slots⁠

SlotDescription
(default)The scrolling content.
block-startA rail pinned before the content on the block axis: a headline, a header bar.
block-endA rail pinned after the content on the block axis: an actions row, a footer.
inline-startA rail pinned before the content on the inline axis. Mirrors under RTL.
inline-endA rail pinned after the content on the inline axis. Mirrors under RTL.

Without a bounded height the region grows with its content and never scrolls.

Events⁠

EventTypeDetailDescription
scroll-extent-changeScrollRegionExtentChangeEventScrollRegionExtentChangeEventDetailFired when either axis starts or stops being able to scroll, or reaches or leaves its start or end. It is not fired for a change in scroll progress, which changes on nearly every frame of a scroll. It bubbles.

The detail carries six booleans: scrollable, scrollableInline, atBlockStart, atBlockEnd, atInlineStart, and atInlineEnd. On an axis that cannot scroll, both of its boundary booleans are true.

Methods⁠

MethodReturnsDescription
measureNow()voidChecks now, rather than on the next frame, whether the content overflows. Call it when your layout depends on the answer, so a scrollbar does not appear in the middle of an animation. It is safe to call before the first render.
resetScroll()voidReturns the scroll position to the start of both axes, in left-to-right and right-to-left text alike. Use it to bring the region back unscrolled, for example when its content is replaced.

Props⁠

PropTypeDescription
blockIndicatorScrollIndicatorTypeScrollIndicator.Divider, .None, .Shadowdefault ScrollIndicator.Shadow, from the style configurationMarks the block-axis edges when content has scrolled past them. It follows the style configuration, as the scroll-indicator of CrDialog and CrSideSheet does, so one configuration restyles all three. See Block indicator.
inlineIndicatorScrollIndicatorTypeScrollIndicator.Divider, .None, .Shadowdefault ScrollIndicator.NoneMarks the inline-axis edges when content has scrolled past them. Unlike blockIndicator, it does not follow the style configuration: the inline axis is opt-in, per region. See Inline indicator.
regionLabelstringThe accessible name of the scrolling area while it takes a tab stop. Set it to say what the area holds, such as a dialog's content. Without one, the area is still focusable and scrollable from the keyboard, but a screen reader does not announce it as a named region. Pass text, not an id. See Naming the region.

A divider shows only at an edge that has a pinned rail, while a shadow shows at any edge content has scrolled past. region-label applies only while content overflows.

Children⁠

ChildDescription
childrenThe scrolling content.
a child with slot="block-start"A rail pinned before the content on the block axis: a headline, a header bar.
a child with slot="block-end"A rail pinned after the content on the block axis: an actions row, a footer.
a child with slot="inline-start"A rail pinned before the content on the inline axis. Mirrors under RTL.
a child with slot="inline-end"A rail pinned after the content on the inline axis. Mirrors under RTL.

Without a bounded height the region grows with its content and never scrolls.

Events⁠

PropArgumentDescription
onScrollExtentChangeScrollRegionExtentChangeEventFired when either axis starts or stops being able to scroll, or reaches or leaves its start or end. It is not fired for a change in scroll progress, which changes on nearly every frame of a scroll. It bubbles. Its detail carries scrollable, scrollableInline, atBlockStart, atBlockEnd, atInlineStart, and atInlineEnd.

The detail carries six booleans: scrollable, scrollableInline, atBlockStart, atBlockEnd, atInlineStart, and atInlineEnd. On an axis that cannot scroll, both of its boundary booleans are true.

Methods⁠

Call these on the element the ref holds.

MethodReturnsDescription
measureNow()voidChecks now, rather than on the next frame, whether the content overflows. Call it when your layout depends on the answer, so a scrollbar does not appear in the middle of an animation. It is safe to call before the first render.
resetScroll()voidReturns the scroll position to the start of both axes, in left-to-right and right-to-left text alike. Use it to bring the region back unscrolled, for example when its content is replaced.

Parameters⁠

ParameterTypeDescription
BlockIndicatorScrollIndicator?ScrollIndicator.Divider, .None, .Shadowdefault ScrollIndicator.Shadow, from the style configurationMarks the block-axis edges when content has scrolled past them. It follows the style configuration, as the scroll-indicator of CbDialog and CbSideSheet does, so one configuration restyles all three. See Block indicator.
InlineIndicatorScrollIndicator?ScrollIndicator.Divider, .None, .Shadowdefault ScrollIndicator.NoneMarks the inline-axis edges when content has scrolled past them. Unlike BlockIndicator, it does not follow the style configuration: the inline axis is opt-in, per region. See Inline indicator.
RegionLabelstring?The accessible name of the scrolling area while it takes a tab stop. Set it to say what the area holds, such as a dialog's content. Without one, the area is still focusable and scrollable from the keyboard, but a screen reader does not announce it as a named region. Pass text, not an id. See Naming the region.

A divider shows only at an edge that has a pinned rail, while a shadow shows at any edge content has scrolled past. region-label applies only while content overflows.

Render fragments⁠

Once you write BlockEnd or any other fragment as a child element, the main content must go in an explicit <ChildContent> too; markup beside <BlockEnd> is a compile error. Here ChildContent is: The scrolling content.

<CbScrollRegion>
    <BlockEnd>Content</BlockEnd>
    <ChildContent>Main content</ChildContent>
</CbScrollRegion>
content_copy
ParameterTypeDescription
BlockEndRenderFragment?A rail pinned after the content on the block axis: an actions row, a footer.
BlockStartRenderFragment?A rail pinned before the content on the block axis: a headline, a header bar.
ChildContentRenderFragment?The scrolling content.
InlineEndRenderFragment?A rail pinned after the content on the inline axis. Mirrors under RTL.
InlineStartRenderFragment?A rail pinned before the content on the inline axis. Mirrors under RTL.

Without a bounded height the region grows with its content and never scrolls.

Event callbacks⁠

ParameterTypeDescription
OnScrollExtentChangeEventCallback<ScrollExtentChangeEventArgs>Fired when either axis starts or stops being able to scroll, or reaches or leaves its start or end. It is not fired for a change in scroll progress, which changes on nearly every frame of a scroll. It bubbles. Its detail carries scrollable, scrollableInline, atBlockStart, atBlockEnd, atInlineStart, and atInlineEnd.

The detail carries six booleans: scrollable, scrollableInline, atBlockStart, atBlockEnd, atInlineStart, and atInlineEnd. On an axis that cannot scroll, both of its boundary booleans are true.

Inputs⁠

InputTypeDescription
blockIndicatorScrollIndicatorTypeScrollIndicator.Divider, .None, .Shadowdefault ScrollIndicator.Shadow, from the style configurationMarks the block-axis edges when content has scrolled past them. It follows the style configuration, as the scroll-indicator of CaDialog and CaSideSheet does, so one configuration restyles all three. See Block indicator.
inlineIndicatorScrollIndicatorTypeScrollIndicator.Divider, .None, .Shadowdefault ScrollIndicator.NoneMarks the inline-axis edges when content has scrolled past them. Unlike blockIndicator, it does not follow the style configuration: the inline axis is opt-in, per region. See Inline indicator.
regionLabelstringThe accessible name of the scrolling area while it takes a tab stop. Set it to say what the area holds, such as a dialog's content. Without one, the area is still focusable and scrollable from the keyboard, but a screen reader does not announce it as a named region. Pass text, not an id. See Naming the region.

A divider shows only at an edge that has a pinned rail, while a shadow shows at any edge content has scrolled past. region-label applies only while content overflows.

Content projection⁠

ContentDescription
content with no slotThe scrolling content.
an element with slot="block-start"A rail pinned before the content on the block axis: a headline, a header bar.
an element with slot="block-end"A rail pinned after the content on the block axis: an actions row, a footer.
an element with slot="inline-start"A rail pinned before the content on the inline axis. Mirrors under RTL.
an element with slot="inline-end"A rail pinned after the content on the inline axis. Mirrors under RTL.

Without a bounded height the region grows with its content and never scrolls.

Outputs⁠

OutputEventDescription
cwScrollExtentChangeScrollRegionExtentChangeEventFired when either axis starts or stops being able to scroll, or reaches or leaves its start or end. It is not fired for a change in scroll progress, which changes on nearly every frame of a scroll. It bubbles. Its detail carries scrollable, scrollableInline, atBlockStart, atBlockEnd, atInlineStart, and atInlineEnd.

The detail carries six booleans: scrollable, scrollableInline, atBlockStart, atBlockEnd, atInlineStart, and atInlineEnd. On an axis that cannot scroll, both of its boundary booleans are true.

Methods⁠

Call these on the element the ref holds.

MethodReturnsDescription
measureNow()voidChecks now, rather than on the next frame, whether the content overflows. Call it when your layout depends on the answer, so a scrollbar does not appear in the middle of an animation. It is safe to call before the first render.
resetScroll()voidReturns the scroll position to the start of both axes, in left-to-right and right-to-left text alike. Use it to bring the region back unscrolled, for example when its content is replaced.

Props⁠

PropTypeDescription
blockIndicatorScrollIndicatorTypeScrollIndicator.Divider, .None, .Shadowdefault ScrollIndicator.Shadow, from the style configurationMarks the block-axis edges when content has scrolled past them. It follows the style configuration, as the scroll-indicator of CvDialog and CvSideSheet does, so one configuration restyles all three. See Block indicator.
inlineIndicatorScrollIndicatorTypeScrollIndicator.Divider, .None, .Shadowdefault ScrollIndicator.NoneMarks the inline-axis edges when content has scrolled past them. Unlike blockIndicator, it does not follow the style configuration: the inline axis is opt-in, per region. See Inline indicator.
regionLabelstringThe accessible name of the scrolling area while it takes a tab stop. Set it to say what the area holds, such as a dialog's content. Without one, the area is still focusable and scrollable from the keyboard, but a screen reader does not announce it as a named region. Pass text, not an id. See Naming the region.

A divider shows only at an edge that has a pinned rail, while a shadow shows at any edge content has scrolled past. region-label applies only while content overflows.

Slots⁠

SlotDescription
defaultThe scrolling content.
block-endA rail pinned after the content on the block axis: an actions row, a footer.
block-startA rail pinned before the content on the block axis: a headline, a header bar.
inline-endA rail pinned after the content on the inline axis. Mirrors under RTL.
inline-startA rail pinned before the content on the inline axis. Mirrors under RTL.

Without a bounded height the region grows with its content and never scrolls.

Events⁠

EventTypeDescription
scrollExtentChangeScrollRegionExtentChangeEventFired when either axis starts or stops being able to scroll, or reaches or leaves its start or end. It is not fired for a change in scroll progress, which changes on nearly every frame of a scroll. It bubbles. Its detail carries scrollable, scrollableInline, atBlockStart, atBlockEnd, atInlineStart, and atInlineEnd.

The detail carries six booleans: scrollable, scrollableInline, atBlockStart, atBlockEnd, atInlineStart, and atInlineEnd. On an axis that cannot scroll, both of its boundary booleans are true.

Methods⁠

Call these on the element the ref holds.

MethodReturnsDescription
measureNow()voidChecks now, rather than on the next frame, whether the content overflows. Call it when your layout depends on the answer, so a scrollbar does not appear in the middle of an animation. It is safe to call before the first render.
resetScroll()voidReturns the scroll position to the start of both axes, in left-to-right and right-to-left text alike. Use it to bring the region back unscrolled, for example when its content is replaced.

Props⁠

PropTypeDescription
blockIndicatorScrollIndicatorTypeScrollIndicator.Divider, .None, .Shadowdefault ScrollIndicator.Shadow, from the style configurationMarks the block-axis edges when content has scrolled past them. It follows the style configuration, as the scroll-indicator of CsDialog and CsSideSheet does, so one configuration restyles all three. See Block indicator.
inlineIndicatorScrollIndicatorTypeScrollIndicator.Divider, .None, .Shadowdefault ScrollIndicator.NoneMarks the inline-axis edges when content has scrolled past them. Unlike blockIndicator, it does not follow the style configuration: the inline axis is opt-in, per region. See Inline indicator.
regionLabelstringThe accessible name of the scrolling area while it takes a tab stop. Set it to say what the area holds, such as a dialog's content. Without one, the area is still focusable and scrollable from the keyboard, but a screen reader does not announce it as a named region. Pass text, not an id. See Naming the region.

A divider shows only at an edge that has a pinned rail, while a shadow shows at any edge content has scrolled past. region-label applies only while content overflows.

Snippets⁠

SnippetDescription
childrenThe scrolling content.
blockStartSnippetA rail pinned before the content on the block axis: a headline, a header bar.
blockEndSnippetA rail pinned after the content on the block axis: an actions row, a footer.
inlineStartSnippetA rail pinned before the content on the inline axis. Mirrors under RTL.
inlineEndSnippetA rail pinned after the content on the inline axis. Mirrors under RTL.

Without a bounded height the region grows with its content and never scrolls.

Events⁠

PropTypeDescription
onScrollExtentChangeScrollRegionExtentChangeEventFired when either axis starts or stops being able to scroll, or reaches or leaves its start or end. It is not fired for a change in scroll progress, which changes on nearly every frame of a scroll. It bubbles. Its detail carries scrollable, scrollableInline, atBlockStart, atBlockEnd, atInlineStart, and atInlineEnd.

The detail carries six booleans: scrollable, scrollableInline, atBlockStart, atBlockEnd, atInlineStart, and atInlineEnd. On an axis that cannot scroll, both of its boundary booleans are true.

Methods⁠

Call these on the element the ref holds.

MethodReturnsDescription
measureNow()voidChecks now, rather than on the next frame, whether the content overflows. Call it when your layout depends on the answer, so a scrollbar does not appear in the middle of an animation. It is safe to call before the first render.
resetScroll()voidReturns the scroll position to the start of both axes, in left-to-right and right-to-left text alike. Use it to bring the region back unscrolled, for example when its content is replaced.

Each attribute value has its own tokens; a cell shows its token's value, and selecting the cell shows the token's fallback path, with each name and value ready to copy. For example, the shadow column's level row is --ct-scroll-region-shadow-level, which defaults to 3.

Indicator⁠

Namedividershadow
color--ct-sys-color-outline-variant --ct-scroll-region-divider-color—
level—3 --ct-scroll-region-shadow-level
opacity—0.8 --ct-scroll-region-shadow-opacity
thickness1px --ct-scroll-region-divider-thickness—
thickness-block—24px --ct-scroll-region-shadow-thickness-block
thickness-inline—24px --ct-scroll-region-shadow-thickness-inline

Other tokens⁠

These stay the same whatever the indicator.

TokenValue
--ct-scroll-region-focus-ring-color--ct-sys-color-on-surface-variant --ct-scroll-region-focus-ring-color

Names and states⁠

No WAI-ARIA Authoring Practices pattern covers a scroll container. While content overflows, the region takes role="region", which the WAI-ARIA Authoring Practices: Landmark Regions guidance describes.

  • The accessible name comes from region-label alone, and only while content overflows.
  • A region with no region-label still takes focus and scrolls from the keyboard, but has no name.
  • Use region-label, not aria-label, to name the region.
  • aria-labelledby cannot name the region, because an ID elsewhere on the page is not found. Use region-label.
  • The scroll indicators are hidden from assistive technology and add nothing for a screen reader.
  • A region whose content fits has no role, no name, and no tab stop.

Keyboard and focus⁠

Key Action
Tab Moves focus to the scroller while content overflows.
Arrow Up, Arrow Down Scrolls 40 pixels along the block axis.
Arrow Left, Arrow Right Scrolls 40 pixels along the inline axis.
Page Up, Page Down Scrolls one view, less 40 pixels.
Space, Shift+Space Scrolls one view down, or one view up.
Home, End Scrolls to the start or the end of the block axis.

These keys act only while the region has focus.

  • Keyboard focus shows a 2 pixel outline that a corner shape does not clip.
  • At the end of travel, a key press scrolls the page instead.
  • A horizontal arrow on a region that scrolls only vertically is left alone.
  • A text field or listbox in the content keeps its own keys, because the region ignores keys pressed anywhere but on the region itself.
  • Scrolling uses the browser's own behavior, so a reduced-motion preference is respected.

Pointer and touch⁠

  • The region is not a target of its own, so WCAG 2.5.8 Target Size (Minimum) applies to the controls in its rails and content, not to the region itself.
  • Wheel and touch scrolling stay inside the region at the end of travel and do not carry on to the page.

Display settings⁠

  • Forced colors: a divider uses CanvasText and keeps its size and place, and the shadow form is replaced by a CanvasText border.
  • Reduced motion: the indicators appear without their fade.
  • Right-to-left: the inline rails swap sides, and resetScroll() and the event's start and end booleans use the logical start in both directions.

Standards⁠

With its defaults, the scroll region meets two WCAG 2.2 success criteria:

One more depends on how you use it: