Shimmering skeleton bars, each already the width of its line, flatten toward the baseline as the real reply blurs up through them line by line. Bars and text share one grid cell per line.
Also known as: ai response skeleton, skeleton loading to content, shimmer skeleton text, placeholder to text animation.
Cold starts come from the 140 MB image.
Move the model weights to a volume and
boot drops to about 900 ms.
<div class="aia-skeleton-to-text" role="status" aria-label="Writing a reply"> <p class="aia-skeleton-to-text-line" style="--i: 0"><span>Cold starts come from the 140 MB image.</span></p> <p class="aia-skeleton-to-text-line" style="--i: 1"><span>Move the model weights to a volume and</span></p> <p class="aia-skeleton-to-text-line" style="--i: 2"><span>boot drops to about 900 ms.</span></p> </div>
.aia-skeleton-to-text {
--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;
display: grid;
gap: 0.3em;
font-size: calc(var(--aia-size, 16px) * 1.05);
}
.aia-skeleton-to-text-line {
display: grid;
width: fit-content;
margin: 0;
line-height: 1.5;
}
.aia-skeleton-to-text-line::before,
.aia-skeleton-to-text-line span {
grid-area: 1 / 1;
}
.aia-skeleton-to-text-line::before {
content: "";
align-self: center;
height: 0.78em;
border-radius: 0.4em;
background: linear-gradient(90deg, color-mix(in srgb, currentColor 9%, transparent) 30%, color-mix(in srgb, currentColor 17%, transparent) 50%, color-mix(in srgb, currentColor 9%, transparent) 70%) 0 0 / 300% 100%;
animation:
aia-skeleton-to-text-shimmer 1.6s linear infinite,
aia-skeleton-to-text-bar 7s infinite both;
animation-delay: 0s, calc(var(--i) * 0.14s);
}
.aia-skeleton-to-text-line span {
white-space: nowrap;
animation: aia-skeleton-to-text-text 7s infinite both;
animation-delay: calc(var(--i) * 0.14s);
}
@keyframes aia-skeleton-to-text-shimmer {
from { background-position: 100% 0; }
to { background-position: -50% 0; }
}
@keyframes aia-skeleton-to-text-bar {
0%, 36% { opacity: 1; transform: none; animation-timing-function: cubic-bezier(0.5, 0, 0.2, 1); }
46%, 88% { opacity: 0; transform: translateY(0.35em) scaleY(0.15); }
96%, 100% { opacity: 1; transform: none; }
}
@keyframes aia-skeleton-to-text-text {
0%, 38% { opacity: 0; transform: translateY(0.25em); filter: blur(0.25em); animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1); }
50%, 86% { opacity: 1; transform: none; filter: blur(0); }
92%, 100% { opacity: 0; transform: none; filter: blur(0.1em); }
}
@media (prefers-reduced-motion: reduce) {
.aia-skeleton-to-text-line::before,
.aia-skeleton-to-text-line span { animation: none; }
.aia-skeleton-to-text-line::before { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.aia-skeleton-to-text, .aia-skeleton-to-text *, .aia-skeleton-to-text::before, .aia-skeleton-to-text::after, .aia-skeleton-to-text *::before, .aia-skeleton-to-text *::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.