mailwal

Your AI chat scrolls wrong. Fix it in 4 lines of CSS

Marko Denic

Your AI chat scrolls wrong. Fix it in 4 lines of CSS

Read on my website / Read time: 4 minutes

Brought to you by:

AI Coding Faster, Shipping Slower?

Explore the 2026 State of DevEx Report with industry leaders

You've streamed the answer word by word, and it looks great until the answer gets longer than the chat box. Then one of two things happens. Either the text keeps streaming below the fold and the user has to keep scrolling down to read it, or you "fix" that with this:

Now the box follows the answer, but it also yanks the user back to the bottom every time a new word arrives, even while they're scrolling up to reread something. So you add a check for "is the user near the bottom?", tune a pixel threshold, and it still flickers.

The browser can do all of this on its own. You just have to flip the box upside down.

The fix

Then delete the scrollTop = scrollHeight line. That's it.

Try the live demo: click "Ask", then scroll up while the answer is still streaming.

Why this works

A normal scroll box rests at the top. When content grows, you stay at the top, and the new stuff piles up out of view.

column-reverse moves the start of the box to the bottom. Now the bottom is the resting position, so while the answer grows, you stay at the bottom and the newest line is always in view.

The moment the user scrolls up, the browser's scroll anchoring takes over: it picks the line they're looking at and keeps it in place while the answer keeps growing below. No threshold, no flicker, nothing dragging them back down.

What each line does

  • display: flex: makes the box a flex container, so flex-direction applies.
  • flex-direction: column-reverse: puts the start of the scroll area at the bottom. This is the line that does the following.
  • overflow-y: auto: makes it a scroll box.
  • overscroll-behavior: contain: when the user scrolls to the top of the conversation, the page behind it stays put. This is the scroll chaining fix, and every chat box needs it.

Why the wrapper matters

column-reverse reverses the order of the box's children. Put your messages straight inside .chat and the oldest one ends up at the bottom.

With one .messages wrapper, the box only has one child to reverse. Your messages keep their normal order on screen, and the visual order matches the DOM. That matters, because screen readers and the Tab key follow the DOM, not what's on screen.

The one line of JavaScript you still need

When the user sends a new message, take them to the bottom even if they had scrolled up:

Yes, 0. In a column-reverse box, 0 is the bottom, and scrollTop goes negative as you scroll up. Remember that when you add a "Jump to latest" button:

Where this comes up

  • AI chat and "Ask AI" boxes: answers that stream in for 20 seconds.
  • Support chat widgets: new messages arrive while the user reads old ones.
  • Live logs and build output: follow the tail, stop when someone scrolls up to read an error.
  • Comment and activity feeds: anything where the newest item goes at the bottom.

The limits

  • Browser support: following the answer works everywhere. Staying put when the user scrolls up relies on scroll anchoring, which works in Chrome, Edge, Firefox and Safari 27. In older Safari, the text the user is reading drifts up while the answer streams.
  • Negative scrollTop: any existing code that reads scrollTop on this box needs updating, since the values are now 0 and below.
  • Short conversations sit at the bottom: the first message appears at the bottom of the box, like in a messaging app, not at the top.

Why this matters

  • It's what users expect: every chat app they use behaves exactly like this.
  • Less code: no scroll math on every chunk, no "near the bottom" check, no threshold to tune.
  • It can't fight the user: the browser decides when to follow, based on where the user actually is.

Open your chat component, search for scrollHeight, and replace that line with these 4 lines of CSS.

Thanks for reading!

See you next Saturday.

Marko Denić

Find me online:
X/Twitter / LinkedIn / GitHub
Tools I use