Reasoning steps blur in one after another down a hairline thread. The step being thought about stays bright with a pinging ring, older ones dim. One registered @property drives it all.
Also known as: ai reasoning animation, chain of thought animation, ai thinking steps, reasoning timeline css.
<div class="aia-reasoning-trace" role="status" aria-label="Reasoning">
<ol class="aia-reasoning-trace-list">
<li style="--i: 0">Reading the stack trace</li>
<li style="--i: 1">refresh() fires twice on window focus</li>
<li style="--i: 2">Both calls race for the same token</li>
<li style="--i: 3">Share one in-flight promise instead</li>
</ol>
</div>@property --aia-reasoning-trace-t {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
.aia-reasoning-trace {
--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-reasoning-trace-t);
animation: aia-reasoning-trace-run 8s linear infinite;
}
.aia-reasoning-trace-list {
position: relative;
display: grid;
gap: 0.75em;
margin: 0;
padding: 0;
list-style: none;
}
.aia-reasoning-trace-list::before {
content: "";
position: absolute;
left: 0.32em;
top: 0.7em;
bottom: 0.7em;
width: 1px;
background: color-mix(in srgb, currentColor 20%, transparent);
transform-origin: top;
transform: scaleY(clamp(0, calc(var(--t) / 3), 1));
}
.aia-reasoning-trace-list li {
/* r: 0 to 1 as the step arrives; cur: 1 while it is the newest */
--r: clamp(0, calc((var(--t) - var(--i)) * 3), 1);
--cur: calc(var(--r) * clamp(0, calc((var(--i) + 1 - var(--t)) * 4), 1));
position: relative;
padding-left: 1.5em;
white-space: nowrap;
opacity: calc(var(--r) * (0.45 + 0.55 * var(--cur)));
transform: translateY(calc((1 - var(--r)) * 0.5em));
filter: blur(calc((1 - var(--r)) * 0.25em));
}
.aia-reasoning-trace-list li::before,
.aia-reasoning-trace-list li::after {
content: "";
position: absolute;
left: 0.1em;
top: 0.48em;
width: 0.44em;
height: 0.44em;
border-radius: 50%;
}
.aia-reasoning-trace-list li::before {
background: var(--c);
}
.aia-reasoning-trace-list li::after {
box-shadow: 0 0 0 1px var(--c);
transform: scale(var(--cur));
animation: aia-reasoning-trace-ping 1.4s cubic-bezier(0.2, 0.6, 0.3, 1) infinite;
}
@keyframes aia-reasoning-trace-run {
0% { --aia-reasoning-trace-t: 0; opacity: 0; }
4% { opacity: 1; }
16% { --aia-reasoning-trace-t: 1; }
32% { --aia-reasoning-trace-t: 2; }
48% { --aia-reasoning-trace-t: 3; }
60% { --aia-reasoning-trace-t: 3.75; }
88% { opacity: 1; }
96%, 100% { --aia-reasoning-trace-t: 3.75; opacity: 0; }
}
@keyframes aia-reasoning-trace-ping {
0% { scale: 1; opacity: 0.9; }
100% { scale: 2.8; opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.aia-reasoning-trace { animation: none; --aia-reasoning-trace-t: 3.75; }
.aia-reasoning-trace-list li::after { animation: none; opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.aia-reasoning-trace, .aia-reasoning-trace *, .aia-reasoning-trace::before, .aia-reasoning-trace::after, .aia-reasoning-trace *::before, .aia-reasoning-trace *::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.