A reply streams in word by word in uneven bursts, each word fading up from a blur while a soft glowing caret rides the newest one, then blinks once the answer is done. No JavaScript.
Also known as: streaming text animation, chatgpt typing animation css, ai text streaming css, llm streaming cursor.
Yes. Runes replace stores for local state in Svelte 5, but keep a store when several components need to share one value.
<div class="aia-soft-caret-stream" role="status" aria-label="Assistant is replying"> <svg class="aia-soft-caret-stream-avatar" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2c.6 5.2 3.8 8.4 10 10-6.2 1.6-9.4 4.8-10 10-.6-5.2-3.8-8.4-10-10 6.2-1.6 9.4-4.8 10-10Z"/></svg> <p class="aia-soft-caret-stream-text"><span style="--i: 0">Yes.</span> <span style="--i: 1">Runes</span> <span style="--i: 2">replace</span> <span style="--i: 3">stores</span> <span style="--i: 4">for</span> <span style="--i: 5">local</span> <span style="--i: 6">state</span> <span style="--i: 7">in</span> <span style="--i: 8">Svelte</span> <span style="--i: 9">5,</span> <span style="--i: 10">but</span> <span style="--i: 11">keep</span> <span style="--i: 12">a</span> <span style="--i: 13">store</span> <span style="--i: 14">when</span> <span style="--i: 15">several</span> <span style="--i: 16">components</span> <span style="--i: 17">need</span> <span style="--i: 18">to</span> <span style="--i: 19">share</span> <span style="--i: 20">one</span> <span style="--i: 21">value.</span> <span class="aia-soft-caret-stream-end" style="--i: 22"></span></p> </div>
@property --aia-soft-caret-stream-t {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
.aia-soft-caret-stream {
--c: var(--aia-color, #7c5cff);
font-size: var(--aia-size, 16px);
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.4;
--t: var(--aia-soft-caret-stream-t);
display: flex;
align-items: flex-start;
gap: 0.7em;
width: 25em;
max-width: 100%;
animation: aia-soft-caret-stream-run 9s linear infinite;
}
.aia-soft-caret-stream-avatar {
flex: none;
width: 1.3em;
height: 1.3em;
margin-top: 0.15em;
fill: var(--c);
}
.aia-soft-caret-stream-text {
margin: 0;
font-size: 1.05em;
line-height: 1.55;
}
.aia-soft-caret-stream-text span {
/* r: the word has arrived; cur: it is the newest word */
--r: clamp(0, calc((var(--t) - var(--i)) * 2.5), 1);
--cur: calc(clamp(0, calc((var(--t) - var(--i)) * 20), 1) * clamp(0, calc((var(--i) + 1 - var(--t)) * 20), 1));
position: relative;
opacity: var(--r);
filter: blur(calc((1 - var(--r)) * 0.18em));
}
.aia-soft-caret-stream-text span::after {
content: "";
position: absolute;
left: calc(100% + 0.22em);
top: 0.2em;
width: 0.5em;
height: 1.05em;
border-radius: 0.25em;
background: var(--c);
box-shadow: 0 0 0.7em color-mix(in srgb, var(--c) 60%, transparent);
opacity: var(--cur);
}
.aia-soft-caret-stream-end {
display: inline-block;
width: 0;
height: 1.2em;
vertical-align: -0.25em;
}
.aia-soft-caret-stream-end::after {
left: 0;
top: 0.08em;
animation: aia-soft-caret-stream-blink 1s cubic-bezier(0.45, 0, 0.55, 1) infinite;
}
@keyframes aia-soft-caret-stream-run {
0% { --aia-soft-caret-stream-t: 0; opacity: 1; }
4% { --aia-soft-caret-stream-t: 0; }
10% { --aia-soft-caret-stream-t: 3; }
14% { --aia-soft-caret-stream-t: 3.4; }
26% { --aia-soft-caret-stream-t: 9; }
31% { --aia-soft-caret-stream-t: 10; }
47% { --aia-soft-caret-stream-t: 16; }
52% { --aia-soft-caret-stream-t: 16.6; }
66% { --aia-soft-caret-stream-t: 22.6; }
90% { opacity: 1; }
97%, 100% { --aia-soft-caret-stream-t: 22.6; opacity: 0; }
}
@keyframes aia-soft-caret-stream-blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.15; }
}
@media (prefers-reduced-motion: reduce) {
.aia-soft-caret-stream { animation: none; --aia-soft-caret-stream-t: 22.6; }
.aia-soft-caret-stream-end::after { animation: none; }
}
@media (prefers-reduced-motion: reduce) {
.aia-soft-caret-stream, .aia-soft-caret-stream *, .aia-soft-caret-stream::before, .aia-soft-caret-stream::after, .aia-soft-caret-stream *::before, .aia-soft-caret-stream *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--aia-color on the element or a parent.
Currently previewing #3b82f6.--aia-size. Every other dimension is in
em, so the whole thing scales together. Currently previewing 16px.transition or animation
shorthand. Staggered delays are relative, so they keep their rhythm.