Nov 2, 2026 · Reflection · 6 min

Designing the scroll

You’re reading the third line of an answer. The model is writing the fortieth. Somewhere between the two, the interface has to decide who owns the scroll position, and it usually decides wrong.

On the rest of the web the page holds still until you move it. Streaming breaks that: the content grows while you read, and every interface has to choose between following the text and leaving you where you are. Most choose following, then fight the reader for the next thirty seconds.

None of this can be decided in a mockup. A frame has no scroll position, no stream, no thumb on the glass. So I built the behaviour first and wrote the rules after. Here they are, with the demos I used to decide.

How did revenue do this month?

Who is scrolling?

The scroll event fires the same way whether the reader moved or your code did. It tells you the position changed. It can’t tell you who changed it.

Intent lives in other events: wheel, touchmove, Page Up, Space, a drag on the scrollbar. Only a person produces those. Listen to them to decide the reader has taken over, and treat scroll as a measurement, never as a signal.

The bug I see most often comes from skipping this. The interface checks “am I at the bottom?” on every scroll event while its own smooth scroll is still on its way to where the end was. The next burst moves the end, the check says no, and the interface concludes the reader scrolled away. Its animation still finishes, a little further down, at an end that is no longer there. Then nothing restarts it. Nobody touched anything.

The panel on the left asks a different question: has the reader made a gesture? As long as the answer is no, it keeps following, however far ahead the text gets for a moment.

How did revenue do this month?

RecommendedListens for intent

How did revenue do this month?

Not recommendedChecks position on scroll

Don’t touch it. Watch the right one get stuck halfway.

Where following stops

Following should stop the moment the reader leaves, and start again when they come back. The two directions don’t use the same rule. Leaving is decided by intent: the reader scrolls up, following stops, whatever the distance. Coming back is decided by position: the reader scrolls back down to the bottom, following resumes.

Which leaves defining “the bottom”. Not zero: scrollTop is fractional on high-density screens and content grows between two frames, so an exact comparison fails, and following never comes back.

Not five lines either: a reader heading down to finish rereading is caught and carried to the end before they are done. The bottom is a band of one line: come back within a line of the end and you are at the end.

How did revenue do this month?

Not recommended0 px

How did revenue do this month?

Recommended1 line

How did revenue do this month?

Not recommended5 lines

Ask, then watch the same reader make the same three moves in each pane.

Hold the line you’re reading

Once the reader has let go, the rule flips. The line they are reading must not move. And things above it keep growing: an image without dimensions, a table filling up, a citation that expands when its source resolves.

Browsers already have a mechanism for this. Scroll anchoring, controlled by overflow-anchor, picks a node in view and keeps it still when content above it changes size.1 Chrome has had it since 56, Firefox since 66. Safari has never shipped it, on the desktop or on iOS.

It isn’t enough on its own. The anchor holds the top of the viewport, not the line you’re reading, so anything that grows between the two still pushes that line down. Reserve the space you already know about: dimensions on every image, the size of a citation card. The anchor is a safety net, not a layout, and on iOS there is no net.

The south is the outlier, and the pricing test explains most of it. [1]Pricing test · south region · started 3 SepDiscounted entry plan, same week as the dip.Everywhere else, returning customers carry the lift on their own.Margin is flat against last quarter, which is what growth from people who were already paying tends to look like.The next lever is the second purchase, not acquisition.

Not recommendedBrowser anchoring

The south is the outlier, and the pricing test explains most of it. [1]Pricing test · south region · started 3 SepDiscounted entry plan, same week as the dip.Everywhere else, returning customers carry the lift on their own.Margin is flat against last quarter, which is what growth from people who were already paying tends to look like.The next lever is the second purchase, not acquisition.

RecommendedReserved space

Run it, and watch the highlighted sentence in each pane.

Or don’t follow at all

There is another answer, and more chat interfaces are moving to it: don’t follow. Put the question at the top of the viewport and let the answer fill the space below.

The reader lands at the top of the answer, where reading starts anyway. For the first screen there is nothing to follow. Past it, they scroll at their own pace.

Either way you need a way back down. A “new content below” control that appears only when something landed out of view, sits just above the composer, and says what arrived, not just an arrow.

Which segment is growing fastest?Returning customers are your strongest segment, up 23% this month.

RecommendedLets go on scroll

Which segment is growing fastest?Returning customers are your strongest segment, up 23% this month.

RecommendedPins the question

Ask once. Read both answers from the top.

The keyboard is a scroll event

On a phone the viewport isn’t fixed. The keyboard opens and the visible area loses between a quarter and a half of its height, depending on the device, the language and whether the suggestion bar is up. There is no number to hard-code, and the last line of the answer disappears behind the composer.

Listen to visualViewport, not window: it is the one that knows. And declare how the page should respond to the keyboard with interactive-widget in the viewport meta tag.2 Chrome has honoured it on Android since 108. iOS Safari ignores it, which is why the first sentence is the one that matters.

Then test it on a device. A desktop browser in mobile mode has no keyboard.

How did revenue do this month?Revenue is up 12% this month. Almost all of the lift came from returning customers, not from new acquisition, and the pattern holds in every region except the south, where a pricing test is still running.The south is worth reading closely. It is the only market on a discounted entry plan this quarter, and the discount started the same week the test did, so the two are probably one story rather than two. If the test ends as planned, the south should rejoin the other regions within a month.Margin is flat against last quarter, which is what growth from people who were already paying tends to look like. The next lever is not acquisition. It is the second purchase: customers who buy twice in their first ninety days are worth three times as much by the end of the year.

RecommendedListens to visualViewport

How did revenue do this month?Revenue is up 12% this month. Almost all of the lift came from returning customers, not from new acquisition, and the pattern holds in every region except the south, where a pricing test is still running.The south is worth reading closely. It is the only market on a discounted entry plan this quarter, and the discount started the same week the test did, so the two are probably one story rather than two. If the test ends as planned, the south should rejoin the other regions within a month.Margin is flat against last quarter, which is what growth from people who were already paying tends to look like. The next lever is not acquisition. It is the second purchase: customers who buy twice in their first ninety days are worth three times as much by the end of the year.

Not recommendedListens to window

Open the keyboard. Look for the last line.

The numbers I use

ConstantValueWhy this value
DETACH_ONwheel · touch · keys · scrollbaronly a person produces them
BOTTOM_BAND1 linewithin a line of the end is the end
REATTACH_ONinside BOTTOM_BANDintent to leave, position to return
FOLLOW_SCROLLa glide that re-aims every framea smooth scroll aimed at where the end was gets left 353 px behind; one that re-aims never does
JUMP_PILLonly when content lands out of viewno new content, no button

Notes

  1. 1W3C, CSS Scroll Anchoring Module Level 1, Editor’s Draft, 30 September 2026.
  2. 2Bramus Van Damme, Prepare for viewport resize behavior changes coming to Chrome on Android, developer.chrome.com, 2022.