Four images generating side by side: each tile shimmers with its own progress bar and resolves at its own pace while a counter reports how many are ready. One @property clock, four start times.
Also known as: ai image grid loading, image generation progress animation css, batch image generation ui, midjourney grid loading.
<div class="aia-image-batch" role="status" aria-label="Generating 4 images">
<div class="aia-image-batch-copy">
<b>Generating 4 images</b>
<span class="aia-image-batch-prompt">Ceramic vase on linen, soft north light</span>
<span class="aia-image-batch-count"></span>
</div>
<div class="aia-image-batch-grid">
<span class="aia-image-batch-tile" style="--s: 0.3; --v: var(--c); --w: #efe7dc"><i class="aia-image-batch-img"></i><i class="aia-image-batch-bar"></i></span>
<span class="aia-image-batch-tile" style="--s: 0.44; --v: #c4704b; --w: #e9e1d6"><i class="aia-image-batch-img"></i><i class="aia-image-batch-bar"></i></span>
<span class="aia-image-batch-tile" style="--s: 0.58; --v: #2f5d62; --w: #e6e3db"><i class="aia-image-batch-img"></i><i class="aia-image-batch-bar"></i></span>
<span class="aia-image-batch-tile" style="--s: 0.7; --v: #d9c7a5; --w: #3b3a40"><i class="aia-image-batch-img"></i><i class="aia-image-batch-bar"></i></span>
</div>
</div>@property --aia-image-batch-t {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
@property --aia-image-batch-n {
syntax: "<integer>";
inherits: false;
initial-value: 0;
}
.aia-image-batch {
--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;
--out: clamp(0, (0.96 - var(--aia-image-batch-t)) * 16, 1);
display: flex;
align-items: center;
gap: 1.2em;
animation: aia-image-batch-clock 6.5s linear infinite;
}
.aia-image-batch-copy {
display: grid;
gap: 0.35em;
width: 10em;
}
.aia-image-batch-copy b {
font-size: 0.95em;
font-weight: 600;
letter-spacing: -0.01em;
}
.aia-image-batch-prompt {
font-size: 0.8em;
line-height: 1.35;
color: color-mix(in srgb, currentColor 60%, transparent);
}
.aia-image-batch-count {
--aia-image-batch-n: calc(
clamp(0, (var(--aia-image-batch-t) - 0.3) * 1000, 1) + clamp(0, (var(--aia-image-batch-t) - 0.44) * 1000, 1) +
clamp(0, (var(--aia-image-batch-t) - 0.58) * 1000, 1) + clamp(0, (var(--aia-image-batch-t) - 0.7) * 1000, 1)
);
margin-top: 0.3em;
font-size: 0.78em;
font-weight: 600;
font-variant-numeric: tabular-nums;
color: var(--c);
counter-reset: aia-image-batch-n var(--aia-image-batch-n);
}
.aia-image-batch-count::before { content: counter(aia-image-batch-n) " of 4 ready"; }
.aia-image-batch-grid {
display: grid;
grid-template-columns: repeat(2, 5.4em);
gap: 0.35em;
}
.aia-image-batch-tile {
--in: calc(clamp(0, (var(--aia-image-batch-t) - var(--s)) * 10, 1) * var(--out));
position: relative;
height: 5.4em;
border-radius: 0.55em;
overflow: hidden;
background: linear-gradient(100deg, color-mix(in srgb, currentColor 7%, transparent) 30%, color-mix(in srgb, currentColor 15%, transparent) 50%, color-mix(in srgb, currentColor 7%, transparent) 70%) 0 0 / 300% 100%;
animation: aia-image-batch-shimmer 1.6s linear infinite;
}
.aia-image-batch-img {
position: absolute;
inset: 0;
background:
radial-gradient(ellipse 9% 5% at 44% 28%, rgba(255, 255, 255, 0.7), transparent 70%),
radial-gradient(ellipse 8% 3% at 50% 22%, color-mix(in srgb, var(--v) 70%, #000) 0 90%, transparent 100%),
radial-gradient(ellipse 22% 30% at 50% 52%, var(--v) 0 97%, transparent 100%),
linear-gradient(90deg, transparent 45.5%, var(--v) 45.5% 54.5%, transparent 54.5%) 0 22% / 100% 22% no-repeat,
radial-gradient(ellipse 30% 6% at 50% 78%, rgba(0, 0, 0, 0.22), transparent 70%),
linear-gradient(180deg, var(--w) 72%, color-mix(in srgb, var(--w) 82%, #000) 72%);
opacity: var(--in);
filter: blur(calc((1 - var(--in)) * 0.6em));
scale: calc(1.08 - var(--in) * 0.08);
}
.aia-image-batch-bar {
position: absolute;
left: 0.45em;
right: 0.45em;
bottom: 0.45em;
height: 0.2em;
border-radius: 1em;
background:
linear-gradient(var(--c), var(--c)) 0 0 / calc(min(var(--aia-image-batch-t) / var(--s), 1) * 100%) 100% no-repeat,
color-mix(in srgb, currentColor 15%, transparent);
opacity: calc(1 - clamp(0, (var(--aia-image-batch-t) - var(--s)) * 10, 1));
}
@keyframes aia-image-batch-clock {
to { --aia-image-batch-t: 1; }
}
@keyframes aia-image-batch-shimmer {
from { background-position: 100% 0; }
to { background-position: -50% 0; }
}
@media (prefers-reduced-motion: reduce) {
.aia-image-batch { animation: none; --aia-image-batch-t: 0.62; }
.aia-image-batch-tile { animation: none; }
}
@media (prefers-reduced-motion: reduce) {
.aia-image-batch, .aia-image-batch *, .aia-image-batch::before, .aia-image-batch::after, .aia-image-batch *::before, .aia-image-batch *::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.