> ## Documentation Index
> Fetch the complete documentation index at: https://smithers.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# MessageScroller

> Anchored conversation scrolling with streaming follow, prepend compensation, edge fades, and jump-to-latest.

`MessageScroller` is the transport-neutral scroll container behind
`ChatTranscript`: it follows new content by default, stops following when the
reader scrolls away, and exposes a jump button without coupling the UI to a
Gateway or streaming client.

```tsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { MessageScroller } from "smithers-orchestrator/ui";

<MessageScroller streaming={pending} firstItemKey={messages[0]?.id}>
  {messages.map((message) => <article key={message.id}>{message.text}</article>)}
</MessageScroller>
```

## Props

| Prop                | Type                           | Default            | Description                                                  |
| ------------------- | ------------------------------ | ------------------ | ------------------------------------------------------------ |
| `stickToBottom`     | `boolean`                      | `true`             | Follow content growth while the reader stays at bottom.      |
| `streaming`         | `boolean`                      | `false`            | Exposes streaming state through `data-streaming`.            |
| `bottomThreshold`   | `number`                       | `24`               | Pixel distance treated as bottom.                            |
| `firstItemKey`      | `string \| number`             | Not set            | Compensates scroll position when older history is prepended. |
| `fade`              | `boolean`                      | `true`             | Enables token-free mask fades at scrollable edges.           |
| `hideJumpToLatest`  | `boolean`                      | `false`            | Hides the jump affordance while away from bottom.            |
| `jumpToLatestLabel` | `string`                       | `"Jump to latest"` | Accessible jump-button label.                                |
| `onFollowChange`    | `(following: boolean) => void` | Not set            | Fires only when follow state transitions, never on mount.    |
| `contentClassName`  | `string`                       | Not set            | Class applied to the inner content element.                  |

The forwarded `MessageScrollerHandle` provides `scrollToBottom(behavior?)` and
`isFollowing()`. `stickToBottom={false}` disables all automatic scrolling while
retaining fades and one-shot jump behavior.

## Compound anatomy

For full control over the transcript layout, compose the scroller from its
parts instead of using the flat form. The provider owns all scroll state; the
parts subscribe to it through context, so they must render inside
`MessageScrollerProvider`.

```tsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
import {
  MessageScrollerProvider,
  MessageScrollerViewport,
  MessageScrollerContent,
  MessageScrollerItem,
  MessageScrollerButton,
} from "smithers-orchestrator/ui";

<MessageScrollerProvider scrollAnchor="bottom" streaming={pending}>
  <MessageScrollerViewport fade>
    <MessageScrollerContent>
      {messages.map((message) => (
        <MessageScrollerItem
          key={message.id}
          messageId={message.id}
          scrollAnchor={message.role === "user"}
        >
          {message.text}
        </MessageScrollerItem>
      ))}
    </MessageScrollerContent>
  </MessageScrollerViewport>
  <MessageScrollerButton target="latest" />
</MessageScrollerProvider>
```

### MessageScrollerProvider

Owns follow state, item registration, visibility tracking, and the imperative
commands. Not a visual element.

| Prop               | Type                           | Default  | Description                                                                      |
| ------------------ | ------------------------------ | -------- | -------------------------------------------------------------------------------- |
| `scrollAnchor`     | `"bottom" \| "none"`           | `"none"` | `"bottom"` pins to the live edge and follows growth; `"none"` never autoscrolls. |
| `streaming`        | `boolean`                      | `false`  | Exposes streaming state to descendants.                                          |
| `initialMessageId` | `string`                       | Not set  | Saved-transcript restore: anchors this registered message on mount.              |
| `bottomThreshold`  | `number`                       | `24`     | Pixel distance treated as bottom.                                                |
| `peekPx`           | `number`                       | `56`     | Previous-item peek (px) applied on jump and restore.                             |
| `onFollowChange`   | `(following: boolean) => void` | Not set  | Fires only when follow state transitions, never on mount.                        |

### MessageScrollerViewport

The scrollable region (`role="region"`, single tab stop,
`aria-label="Conversation messages"`). Forwards the ref and all `div` props
except `onScroll`.

| Prop   | Type      | Default | Description                                                                       |
| ------ | --------- | ------- | --------------------------------------------------------------------------------- |
| `fade` | `boolean` | `false` | Edge fade masks driven by scroll position (`data-fade-top` / `data-fade-bottom`). |

### MessageScrollerContent

The message list container. Accepts all `div` props; `role` defaults to
`"log"` for live-log semantics.

### MessageScrollerItem

One registered message wrapper with `content-visibility` optimization. Accepts
all `div` props.

| Prop            | Type      | Default  | Description                                                                                                                                                        |
| --------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `messageId`     | `string`  | Required | Stable message identity used by jump, restore, and visibility.                                                                                                     |
| `intrinsicSize` | `number`  | `96`     | Placeholder height hint (px) for content-visibility.                                                                                                               |
| `scrollAnchor`  | `boolean` | `false`  | Marks the row as a turn start: when appended at the live edge it moves near the viewport top with a previous-item peek and holds while the reply streams in below. |

### MessageScrollerButton

Jump affordance that stays rendered and goes inert (`data-active="false"`, no
focus stop) while there is nothing to scroll toward. Accepts all `button`
props except `onClick`.

| Prop       | Type                                           | Default    | Description                                                                            |
| ---------- | ---------------------------------------------- | ---------- | -------------------------------------------------------------------------------------- |
| `target`   | `"latest" \| "start" \| { messageId: string }` | `"latest"` | Where the jump goes; a `messageId` target scrolls that message to the top with a peek. |
| `behavior` | `ScrollBehavior`                               | `"smooth"` | Scroll behavior; `"smooth"` degrades to `"auto"` under reduced motion.                 |

### Hooks

| Hook                              | Returns                                         | Description                                                                                                                 |
| --------------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `useMessageScroller()`            | `MessageScrollerCommands`                       | Imperative `scrollToBottom`, `scrollToTop`, and `scrollToMessage(messageId, { behavior, peek })` from the ambient provider. |
| `useMessageVisibility(messageId)` | `boolean`                                       | Whether a registered message is at least half visible (or fills the viewport).                                              |
| `useMessageScrollerState()`       | `{ atTop, atBottom, following, autoscrolling }` | Viewport position and follow state; subscribes only the calling component.                                                  |

### Flat-form compatibility

The flat `MessageScroller` documented above is unchanged and remains the
drop-in scrollport. Its props map onto the compound anatomy:
`stickToBottom` is `scrollAnchor="bottom"` on the provider, `fade`,
`contentClassName`, `hideJumpToLatest`, and `jumpToLatestLabel` configure the
built-in viewport, content, and jump button, and the handle exposes the same
commands as `useMessageScroller()`. When a flat `MessageScroller` renders
inside an ambient `MessageScrollerProvider` it does not nest a second
scroller: it becomes a styled frame (`data-slot="message-scroller"` with
`data-following` / `data-streaming` / `data-autoscrolling`) and renders its
compound-anatomy children verbatim against the ambient provider's state.
