Message Scroller
Overview
Section titled “Overview”MessageScroller is a managed scroll region for conversational transcripts. It anchors each newly appended turn near the top of the viewport, follows the live edge while content streams in but only while the reader is already there, and preserves the currently visible row when older messages are prepended. The scroll hot path runs entirely in the engine, so transcript rows never re-render on scroll or stream.
Anatomy
Section titled “Anatomy”@using Navius.Primitives.Components.MessageScroller
<NaviusMessageScrollerProvider> <NaviusMessageScroller> <NaviusMessageScrollerViewport> <NaviusMessageScrollerContent> <NaviusMessageScrollerItem /> </NaviusMessageScrollerContent> </NaviusMessageScrollerViewport> <NaviusMessageScrollerButton /> </NaviusMessageScroller></NaviusMessageScrollerProvider>Provider
Section titled “Provider”Cascades the scroll options and the shared context to every descendant. Renders no DOM of its own.
| Prop | Type | Default | Description |
|---|---|---|---|
AutoScroll |
bool |
false |
Follows new content while the reader is already at the live edge. Wheel, touch, keyboard scrolling, scrollbar drags, and explicit jumps release the follow. |
DefaultScrollPosition |
string |
"end" |
Opening position on the first non-empty render: "start", "end", or "last-anchor". |
ScrollEdgeThreshold |
double |
8 |
Distance in px from either edge that still counts as being at it. Drives the scrollable-edge state and the button’s active state. |
ScrollMargin |
double |
0 |
Margin in px applied to the aligned edge for scroll targets and the visibility reading line. |
ScrollPreviousItemPeek |
double |
64 |
Extra margin added to ScrollMargin when a newly appended anchor row is positioned, so part of the previous item stays visible above it. |
ChildContent |
RenderFragment? |
- | The scroller’s parts. |
The scroller frame: the positioning parent for the scroll button and the mirror
target for the scrollable-edge and autoscrolling state. Must sit in a
height-constrained container. Renders a div.
| Prop | Type | Default | Description |
|---|---|---|---|
ChildContent |
RenderFragment? |
- | The Viewport and Button. |
Attributes |
IDictionary<string, object>? |
- | Forwarded to the rendered div. |
Viewport
Section titled “Viewport”The scroll container: a labelled, keyboard-focusable scroll region. Give it a vertical
overflow and a bounded size. Renders a div.
| Prop | Type | Default | Description |
|---|---|---|---|
PreserveScrollOnPrepend |
bool |
true |
Keeps the first visible row stable when older rows are prepended, keyed on MessageId. |
ChildContent |
RenderFragment? |
- | The Content. |
Attributes |
IDictionary<string, object>? |
- | Forwarded to the rendered div. Overrides role, aria-label, or tabindex if supplied. |
Content
Section titled “Content”The transcript container: a live region so appended rows announce without narrating
every streamed token. Every direct child must be an Item. Renders a div.
| Prop | Type | Default | Description |
|---|---|---|---|
SpacerClass |
string? |
- | Class applied to the internal, engine-sized spacer element that makes room for anchored rows. |
ChildContent |
RenderFragment? |
- | The transcript Items. |
Attributes |
IDictionary<string, object>? |
- | Forwarded to the rendered div. Pass aria-busy here while a turn streams if announcements should wait for the completed row. |
One transcript row boundary: a message, a marker, a typing indicator, a separator, or
a load-earlier row. Renders a div.
| Prop | Type | Default | Description |
|---|---|---|---|
MessageId |
string? |
- | Stable row id used by jump-to-message, visibility tracking, and prepend preservation. |
ScrollAnchor |
bool |
false |
Marks the row as a turn boundary that anchors newly appended turns. |
ChildContent |
RenderFragment? |
- | The row’s content. |
Attributes |
IDictionary<string, object>? |
- | Forwarded to the rendered div. |
Button
Section titled “Button”The scroll-to-start/end control. Context-aware: active only while the viewport can
still scroll toward its direction; inert when it can’t. Renders a button.
| Prop | Type | Default | Description |
|---|---|---|---|
Direction |
string |
"end" |
Which edge the button scrolls toward: "end" or "start". |
Behavior |
string |
"smooth" |
Scroll behavior for the jump: "smooth" or "auto". |
ChildContent |
RenderFragment? |
- | Button label or icon content. |
Attributes |
IDictionary<string, object>? |
- | Forwarded to the rendered button. |
Data attributes
Section titled “Data attributes”| Part | Attribute | Description |
|---|---|---|
| Root | data-scrollable |
"start" | "end" | "start end" | absent, mirrored with the Viewport. |
| Root | data-autoscrolling |
Present while the viewport is programmatically scrolling to the live edge, mirrored with the Viewport. |
| Viewport | data-scrollable |
"start" | "end" | "start end" | absent, mirrored with the Root. |
| Viewport | data-autoscrolling |
Present while the viewport is programmatically scrolling to the live edge, mirrored with the Root. |
| Content | data-navius-messagescroller-spacer |
Present on the internal, engine-sized spacer that makes room for anchored rows; style it via SpacerClass. |
| Item | data-message-id |
Mirrors MessageId. |
| Item | data-scroll-anchor |
"true" | "false", mirrors ScrollAnchor. |
| Button | data-direction |
"start" | "end". |
| Button | data-active |
"true" | "false", whether the button can currently scroll; false also sets tabindex="-1" and inert. |
Keyboard interactions
Section titled “Keyboard interactions”| Key | Behavior |
|---|---|
Space / Enter |
When focus is on the Button, activates it and scrolls toward its configured direction. |
ARIA mechanism
Section titled “ARIA mechanism”The Viewport renders role="region", aria-label="Messages", and tabindex="0"
(each overridable through Attributes). The Content renders role="log" and
aria-relevant="additions" so appended rows announce without narrating every
streamed token. The Button is a native button that goes inert with tabindex="-1"
when it cannot scroll in its direction, so it never becomes a ghost focus stop. The
browser’s own scroll keys (arrows, Page Up/Down, Home/End) apply when the Viewport or
a focusable child has focus; Navius does not intercept them, it only reacts to them to
release the streamed-reply follow. This mechanism is covered by the Playwright browser
test suite and gated by axe-core in CI.