DeltaKitDeltaKit
React

Auto Scroll

Keep the chat scrolled to the bottom during streaming.

useAutoScroll keeps your scroll container pinned to the bottom as new content streams in.

Basic Usage

import { useStreamChat, useAutoScroll } from "@deltakit/react";

function Chat() {
  const { messages, sendMessage } = useStreamChat({ api: "/api/chat" });
  const { ref, scrollToBottom, isAtBottom } = useAutoScroll([messages]);

  return (
    <div className="flex h-screen flex-col">
      <div ref={ref} className="flex-1 overflow-y-auto">
        {messages.map((msg) => (
          <div key={msg.id}>
            <strong>{msg.role}:</strong>{" "}
            {msg.parts
              .filter((p) => p.type === "text")
              .map((p) => p.text)
              .join("")}
          </div>
        ))}
      </div>

      <form
        onSubmit={(e) => {
          e.preventDefault();
          const input = e.currentTarget.elements.namedItem(
            "message",
          ) as HTMLInputElement;
          if (!input.value.trim()) return;
          sendMessage(input.value);
          scrollToBottom();
          input.value = "";
        }}
      >
        <input name="message" placeholder="Type a message..." />
        <button type="submit">Send</button>
      </form>
    </div>
  );
}

How It Works

The hook tracks whether the user is near the bottom of the scroll container:

  • Pinned (default) — as content grows, the view stays at the bottom.
  • Detached — when the user scrolls up, or wheels upward during active pinning, auto-scroll disengages so they can read earlier messages undisturbed.
  • Re-pinned — when the user scrolls back near the bottom (within threshold pixels), auto-scroll re-engages automatically.

Internally the hook uses a MutationObserver and ResizeObserver, so it catches content changes during streaming even between React re-renders (e.g. DOM mutations from markdown renderers).

Scroll to Bottom on Send

The hook does not automatically scroll when a new user message is sent — it only reacts to scroll position. Call scrollToBottom() in your submit handler to re-pin:

const handleSubmit = (text: string) => {
  sendMessage(text);
  scrollToBottom();
};

Disabling Auto Scroll

Pass enabled: false to turn off auto-scroll while keeping the ref attached:

const { ref } = useAutoScroll([messages], { enabled: false });

Smooth Scrolling

During streaming, the hook keeps the viewport pinned using instant scrollTop updates to avoid flicker from overlapping smooth-scroll animations. The behavior option controls only the explicit scrollToBottom() call — use "smooth" (default) for an animated jump back, or "instant" to snap immediately:

const { ref, scrollToBottom } = useAutoScroll([messages], { behavior: "smooth" });

Recipes

For common auto-scroll patterns, see these recipes:

API Reference

See useAutoScroll API Reference for full options and return values.

On this page