On the typography of streaming tokens
Why a cursor that flickers like film grain feels more alive than a smooth blink.
hen the model starts thinking, the page is not finished · it is becoming. That hand-off, from a static document to a living surface, asks the typography to do something it has never had to do before. It has to breathe.
id: streaming-type title: "On the typography of streaming tokens" sub: "Why a cursor that flickers like film grain feels more alive than a smooth blink." pub: "Personal" date: "May 14, 2026" read: "12 min" wordCount: 2840 toc:
- id: the-becoming title: "The page is becoming"
- id: cursor-as-instrument title: "The cursor as instrument"
- id: film-grain title: "Why film grain"
- id: three-decisions title: "Three small decisions"
- id: open title: "Open questions" related:
- id: how-i-work-now pub: "Personal" read: "8 min" title: "How I Work Now" sub: "A designer's snapshot of six months in." prev: null next: null
When the model starts thinking, the page is not finished · it is becoming. That hand-off, from a static document to a living surface, asks the typography to do something it has never had to do before. It has to breathe.
The page is becoming
For thirty years, the rule was simple: text appears on screen the way it appears on paper. It arrives all at once, fully formed, and then sits there. The only moving thing is the cursor, and the cursor is a convention so old that we have stopped noticing it.
A streamed token output breaks that contract. The paragraph composes itself in front of you. You are watching a sentence figure out where it wants to go. The static document is a category, the streaming response is something else · call it a becoming page, in the gerund.1
The interesting design question is what the typography should do during the becoming. Most products solve this by ignoring it: serif body, sans UI, a fixed-width caret blinking at the end of the buffer at exactly 1Hz. The page pretends the response is finished even while it is being written.
I think that is a missed opportunity.
The cursor as instrument
A cursor is two things at once: a position indicator and an aliveness indicator. The first is geometric, the second is psychological. When the model is thinking, the cursor is the only place the user is allowed to look · so the cursor has to carry the weight of saying "I am working" without saying "I am stuck."
“
A smooth blink reads as machine; an irregular pulse reads as breath. The eye cannot tell you why one is friendlier, but the body knows immediately.
”
I started by trying every easing curve I could find. Linear blinks felt clinical. Sine-eased blinks felt like an iPhone notification. Tween-bounced cursors felt like a video game level-up. None of them were right.
Then I tried film grain.
Why film grain
Film grain is not regular. It is a noise function · a stochastic flicker that the eye reads as light passing through something physical. Take an image, add grain, and the same image suddenly looks like it was captured from the world instead of generated in software. The grain says: this is matter, not math.2
A cursor with film-grain modulation has the same effect at a much smaller scale. Instead of fading from 1.0 to 0.0 opacity on a sine curve, it fades along a noise function bounded between 0.7 and 1.0. The visual difference is small. The felt difference is enormous.
/* A cursor that breathes instead of blinks. */
.cursor {
animation: grain 2.4s steps(12) infinite;
}
@keyframes grain {
0% { opacity: 0.92 }
18% { opacity: 0.74 }
36% { opacity: 1.00 }
54% { opacity: 0.81 }
72% { opacity: 0.96 }
100% { opacity: 0.92 }
}A user testing the prototype said it looked like a candle. I had not used the word "candle" anywhere in the design, but the metaphor was already there: the cursor as a small live flame, present and slightly unstable, indicating that something behind the surface is alive.
Three small decisions
Three smaller calls compound with the grain to make the streaming surface feel like a thing being made rather than a thing being delivered:
- Letter-spacing widens slightly as the buffer grows. Each token added increases the running line's tracking by 0.002em, capped at 0.04em over a long paragraph. The eye does not consciously register the widening, but the line feels less rigid as it lengthens · as if the type itself were settling in.
- Italic punctuation is held back by one frame. When the model emits a comma or a period, the punctuation glyph arrives 16ms after the preceding letter. It looks like the writer paused for breath. This effect is invisible to anyone who is not designing for it, and immediately obvious once you look for it.
- The final paragraph briefly settles. When the stream completes, the entire response shifts down by half a pixel and the cursor fades out over 280ms. It is the typographic equivalent of putting the pen down.
Open questions
A few things I am still unsure about:
- Does the grain effect survive at 144Hz refresh rates? My hunch is yes, but I have not tested.
- What does the cursor do when the model is interrupted by the user mid-sentence? A clean cut feels rude. A fade feels evasive.
- In a multi-agent terminal, each agent has its own cursor. Should the grain frequencies be coprime so they never sync up by accident?
I will write more on each of these. For now: the streaming response is the new typographic surface, and we are early enough that the small decisions still get to count.
A cursor that breathes is a tiny thing. But everything important on the screen passes through it.
Footnotes
-
A few of the citations here will rotate as I keep reading. The current shortlist: Tufte on micro/macro readings, Bringhurst on the open page, and a 2019 paper from Jung et al. on the perceptual psychology of irregular motion. ↩
-
For a primer on noise functions in design, see Inigo Quilez's shadertoy essays. The math is small; the consequences are not. ↩