Word-shaped tokens stream in from blur, arriving in bursts like a model writing, and the newest ones flash brighter before settling. Driven by one registered @property.
Also known as: ai streaming text animation, chatgpt response loading, token streaming effect.
<div class="ldr-streaming-response" role="status" aria-label="Generating response"> <div class="ldr-streaming-response-line"><span style="--i: 0; --w: 4"></span><span style="--i: 1; --w: 6"></span><span style="--i: 2; --w: 3"></span><span style="--i: 3; --w: 5"></span></div> <div class="ldr-streaming-response-line"><span style="--i: 4; --w: 6"></span><span style="--i: 5; --w: 3"></span><span style="--i: 6; --w: 4"></span><span style="--i: 7; --w: 6"></span></div> <div class="ldr-streaming-response-line"><span style="--i: 8; --w: 5"></span><span style="--i: 9; --w: 4"></span></div> </div>
@property --ldr-streaming-response-t {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
.ldr-streaming-response {
--s: var(--ldr-size, 48px);
--c: var(--ldr-color, #3b82f6);
display: grid;
gap: calc(var(--s) / 8);
animation: ldr-streaming-response-stream 3.6s linear infinite;
}
.ldr-streaming-response-line {
display: flex;
gap: calc(var(--s) / 10);
}
.ldr-streaming-response-line span {
/* r: 0 to 1 as this token arrives; fresh: 1 right after it lands, fading to 0 */
--r: clamp(0, calc(var(--ldr-streaming-response-t) * 11 - var(--i)), 1);
--fresh: calc(1 - clamp(0, calc((var(--ldr-streaming-response-t) * 11 - var(--i) - 1) / 2), 1));
width: calc(var(--s) * var(--w) / 6);
height: calc(var(--s) / 7);
border-radius: calc(var(--s) / 14);
background: color-mix(in srgb, var(--c) calc(40% + var(--fresh) * 60%), transparent);
opacity: var(--r);
transform: translateY(calc((1 - var(--r)) * var(--s) / 10));
filter: blur(calc((1 - var(--r)) * var(--s) / 14));
}
@keyframes ldr-streaming-response-stream {
0% { --ldr-streaming-response-t: 0; opacity: 1; }
18% { --ldr-streaming-response-t: 0.32; }
28% { --ldr-streaming-response-t: 0.36; }
52% { --ldr-streaming-response-t: 0.8; }
60% { --ldr-streaming-response-t: 0.84; }
72% { --ldr-streaming-response-t: 1; }
88% { opacity: 1; }
100% { --ldr-streaming-response-t: 1; opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.ldr-streaming-response, .ldr-streaming-response *, .ldr-streaming-response::before, .ldr-streaming-response::after, .ldr-streaming-response *::before, .ldr-streaming-response *::after {
animation-duration: 3s;
}
}--ldr-color on the element or a parent.
Currently previewing #3b82f6.--ldr-size. Every dimension in the snippet is
derived from it. Currently previewing 64px.animation shorthand.
Delays on the children are relative, so they keep their rhythm.role="status" and an
aria-label so screen readers announce the wait. The reduced-motion rule
at the end slows the animation for users who asked for less motion.