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.
Reference docs
Built in
andblock-indicator each mark the edges content has scrolled past with a divider, a shadow, or nothing.inline-indicator- 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
, and keyboard scrolling.region-label scroll-extent-changefires 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
form per surface: a divider or a shadow under a pinned bar, never both.block-indicator - Reserve dividers for edges where a rail is pinned, and let a shadow mark an edge where content simply continues.
- Leave
at none unless the content overflows sideways.inline-indicator - 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
describing its content.region-label
Block indicator
block-indicatordividershadownoneshadow
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-indicatornone
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
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
| Property | Attribute | Type | Description |
|---|---|---|---|
blockIndicator | block-indicatordivider, none, shadow | ScrollIndicatorScrollIndicator.Divider, .None, .Shadowdefault ScrollIndicator.Shadow, from the style configuration | Marks 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. |
inlineIndicator | inline-indicatordivider, none, shadow | ScrollIndicatorScrollIndicator.Divider, .None, .Shadowdefault ScrollIndicator.None | Marks 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. |
regionLabel | region-label | string | 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
Slots
| Slot | Description |
|---|---|
| (default) | The scrolling content. |
block-start | A rail pinned before the content on the block axis: a headline, a header bar. |
block-end | A rail pinned after the content on the block axis: an actions row, a footer. |
inline-start | A rail pinned before the content on the inline axis. Mirrors under RTL. |
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
| Event | Type | Detail | Description |
|---|---|---|---|
scroll-extent-change | ScrollRegionExtentChangeEvent | ScrollRegionExtentChangeEventDetail | 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. |
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
| Method | Returns | Description |
|---|---|---|
measureNow() | void | Checks 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() | void | Returns 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
| Prop | Type | Description |
|---|---|---|
blockIndicator | ScrollIndicatorTypeScrollIndicator.Divider, .None, .Shadowdefault ScrollIndicator.Shadow, from the style configuration | Marks 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. |
inlineIndicator | ScrollIndicatorTypeScrollIndicator.Divider, .None, .Shadowdefault ScrollIndicator.None | Marks 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. |
regionLabel | string | 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
Children
| Child | Description |
|---|---|
children | The 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
| Prop | Argument | Description |
|---|---|---|
onScrollExtentChange | ScrollRegionExtentChangeEvent | 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.
Methods
Call these on the element the ref holds.
| Method | Returns | Description |
|---|---|---|
measureNow() | void | Checks 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() | void | Returns 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
| Parameter | Type | Description |
|---|---|---|
BlockIndicator | ScrollIndicator?ScrollIndicator.Divider, .None, .Shadowdefault ScrollIndicator.Shadow, from the style configuration | Marks 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. |
InlineIndicator | ScrollIndicator?ScrollIndicator.Divider, .None, .Shadowdefault ScrollIndicator.None | Marks 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. |
RegionLabel | string? | 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
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>
| Parameter | Type | Description |
|---|---|---|
BlockEnd | RenderFragment? | A rail pinned after the content on the block axis: an actions row, a footer. |
BlockStart | RenderFragment? | A rail pinned before the content on the block axis: a headline, a header bar. |
ChildContent | RenderFragment? | The scrolling content. |
InlineEnd | RenderFragment? | A rail pinned after the content on the inline axis. Mirrors under RTL. |
InlineStart | RenderFragment? | 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
| Parameter | Type | Description |
|---|---|---|
OnScrollExtentChange | EventCallback<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
| Input | Type | Description |
|---|---|---|
blockIndicator | ScrollIndicatorTypeScrollIndicator.Divider, .None, .Shadowdefault ScrollIndicator.Shadow, from the style configuration | Marks 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. |
inlineIndicator | ScrollIndicatorTypeScrollIndicator.Divider, .None, .Shadowdefault ScrollIndicator.None | Marks 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. |
regionLabel | string | 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
Content projection
| Content | Description |
|---|---|
content with no slot | The 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
| Output | Event | Description |
|---|---|---|
cwScrollExtentChange | ScrollRegionExtentChangeEvent | 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.
Methods
Call these on the element the ref holds.
| Method | Returns | Description |
|---|---|---|
measureNow() | void | Checks 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() | void | Returns 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
| Prop | Type | Description |
|---|---|---|
blockIndicator | ScrollIndicatorTypeScrollIndicator.Divider, .None, .Shadowdefault ScrollIndicator.Shadow, from the style configuration | Marks 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. |
inlineIndicator | ScrollIndicatorTypeScrollIndicator.Divider, .None, .Shadowdefault ScrollIndicator.None | Marks 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. |
regionLabel | string | 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
Slots
| Slot | Description |
|---|---|
default | The scrolling content. |
block-end | A rail pinned after the content on the block axis: an actions row, a footer. |
block-start | A rail pinned before the content on the block axis: a headline, a header bar. |
inline-end | A rail pinned after the content on the inline axis. Mirrors under RTL. |
inline-start | 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.
Events
| Event | Type | Description |
|---|---|---|
scrollExtentChange | ScrollRegionExtentChangeEvent | 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.
Methods
Call these on the element the ref holds.
| Method | Returns | Description |
|---|---|---|
measureNow() | void | Checks 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() | void | Returns 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
| Prop | Type | Description |
|---|---|---|
blockIndicator | ScrollIndicatorTypeScrollIndicator.Divider, .None, .Shadowdefault ScrollIndicator.Shadow, from the style configuration | Marks 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. |
inlineIndicator | ScrollIndicatorTypeScrollIndicator.Divider, .None, .Shadowdefault ScrollIndicator.None | Marks 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. |
regionLabel | string | 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
Snippets
| Snippet | Description |
|---|---|
children | The scrolling content. |
blockStartSnippet | A rail pinned before the content on the block axis: a headline, a header bar. |
blockEndSnippet | A rail pinned after the content on the block axis: an actions row, a footer. |
inlineStartSnippet | A rail pinned before the content on the inline axis. Mirrors under RTL. |
inlineEndSnippet | 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
| Prop | Type | Description |
|---|---|---|
onScrollExtentChange | ScrollRegionExtentChangeEvent | 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.
Methods
Call these on the element the ref holds.
| Method | Returns | Description |
|---|---|---|
measureNow() | void | Checks 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() | void | Returns 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
| Name | divider | shadow |
|---|---|---|
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 |
thickness | 1px --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.
| Token | Value |
|---|---|
--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
alone, and only while content overflows.region-label - A region with no
still takes focus and scrolls from the keyboard, but has no name.region-label - Use
, notregion-labelaria-label, to name the region. aria-labelledbycannot 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
CanvasTextand keeps its size and place, and the shadow form is replaced by aCanvasTextborder. - 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:
- 4.1.2 Name, Role, Value (A): set
so the focusable region has a name.region-label