Back to the library
Job complete, quiet
Variant: Quiet
A build row on an ops dashboard: the clay rail reaches full, a tick draws in beside the label, and the rail dims to a third of its weight — because finishing means it stops being live. Every beat lands in 140-180ms and nothing scales, glows or celebrates. Reach for it where the same event fires hundreds of times a day and the only correct behaviour is to acknowledge and get out of the way.
successstatuscontrolsvg
The source
A complete HTML document. Paste it into an empty .html file and it plays — no stylesheet, script, font, or image to fetch.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Job complete — understated</title>
<style>
:root {
--ivory: #faf9f5;
--slate: #141413;
--clay: #d97757;
--oat: #e3dacc;
/* One cycle. The event itself occupies ~400ms of it; the rest is the
row simply sitting there, which is what this row does all day. */
--cycle: 6s;
--out: cubic-bezier(0.16, 0.84, 0.3, 1);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
min-height: 100vh;
display: grid;
place-items: center;
background: var(--ivory);
font-family:
system-ui,
-apple-system,
'Segoe UI',
Roboto,
sans-serif;
color: var(--slate);
}
.row {
/* Grows with the stage instead of sitting at 200px in a 1100px frame.
Floors at the original width, so the two mobile sizes are unchanged. */
width: clamp(200px, 34vw, 340px);
display: grid;
gap: 10px;
/* The loop restarts from opacity 0, so the reset is never seen. */
animation: card var(--cycle) linear infinite;
}
@keyframes card {
0% {
opacity: 0;
}
2.5% {
opacity: 1;
}
96% {
opacity: 1;
animation-timing-function: ease;
}
100% {
opacity: 0;
}
}
.head {
display: flex;
align-items: baseline;
justify-content: space-between;
font-size: clamp(12px, 1.85vw, 18px);
letter-spacing: -0.01em;
}
.head .name {
font-weight: 550;
}
.head .id {
font-size: clamp(11px, 1.8vw, 17px);
color: color-mix(in srgb, var(--slate) 45%, transparent);
font-variant-numeric: tabular-nums;
}
.rail {
height: 2px;
border-radius: 1px;
background: var(--oat);
overflow: hidden;
}
.rail i {
display: block;
height: 100%;
background: var(--clay);
transform-origin: left center;
animation: fill var(--cycle) linear infinite;
}
/* The only "event" gesture: the last sliver of the bar lands (180ms),
then the bar steps back to a resting tint. No pop, no colour flood. */
@keyframes fill {
0%,
8% {
transform: scaleX(0.93);
opacity: 1;
animation-timing-function: var(--out);
}
11% {
transform: scaleX(1);
opacity: 1;
animation-timing-function: ease;
}
12.5% {
transform: scaleX(1);
opacity: 1;
}
15%,
100% {
transform: scaleX(1);
opacity: 0.32;
}
}
.foot {
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
font-size: clamp(11px, 1.8vw, 17px);
font-variant-numeric: tabular-nums;
}
.states {
display: grid;
}
.states > span {
grid-area: 1 / 1;
display: flex;
align-items: center;
gap: 5px;
line-height: 1.2;
}
.running {
color: color-mix(in srgb, var(--slate) 55%, transparent);
animation: state-out var(--cycle) linear infinite;
}
@keyframes state-out {
0%,
8.5% {
opacity: 1;
transform: translateY(0);
animation-timing-function: ease;
}
10.5%,
100% {
opacity: 0;
transform: translateY(-2px);
}
}
.done {
font-weight: 550;
animation: done var(--cycle) linear infinite;
}
@keyframes done {
0%,
10% {
opacity: 0;
transform: translateY(3px);
animation-timing-function: var(--out);
}
12.3%,
100% {
opacity: 1;
transform: translateY(0);
}
}
.tick {
/* Tracks the foot type size so the mark stays cap-height next to it. */
width: clamp(11px, 1.8vw, 17px);
height: clamp(11px, 1.8vw, 17px);
flex: none;
overflow: visible;
}
.tick path {
fill: none;
stroke: var(--clay);
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
stroke-dasharray: 16;
animation: draw var(--cycle) linear infinite;
}
@keyframes draw {
0%,
10.5% {
stroke-dashoffset: 16;
animation-timing-function: var(--out);
}
13.2%,
100% {
stroke-dashoffset: 0;
}
}
.time {
color: color-mix(in srgb, var(--slate) 45%, transparent);
}
@media (prefers-reduced-motion: reduce) {
/* Gentler, not dead: the state still changes, it just crossfades. */
.rail i {
animation: fill-rm var(--cycle) linear infinite;
transform: scaleX(1);
}
@keyframes fill-rm {
0%,
10% {
opacity: 1;
}
16%,
100% {
opacity: 0.32;
}
}
.running {
animation: running-rm var(--cycle) linear infinite;
}
@keyframes running-rm {
0%,
9% {
opacity: 1;
}
13%,
100% {
opacity: 0;
}
}
.done {
animation: done-rm var(--cycle) linear infinite;
transform: none;
}
@keyframes done-rm {
0%,
10% {
opacity: 0;
}
15%,
100% {
opacity: 1;
}
}
.tick path {
animation: none;
stroke-dashoffset: 0;
}
}
</style>
</head>
<body>
<div class="row" role="status" aria-label="Deploy to production finished successfully">
<div class="head">
<span class="name">deploy · production</span>
<span class="id">#4912</span>
</div>
<div class="rail"><i></i></div>
<div class="foot">
<div class="states">
<span class="running">running</span>
<span class="done">
<svg class="tick" viewBox="0 0 16 16" aria-hidden="true">
<path d="M3.5 8.5 L6.5 11.5 L12.5 4.5" />
</svg>
passed
</span>
</div>
<span class="time">3m 14s</span>
</div>
</div>
</body>
</html>
The variant family
Same idea, one axis moved. Compare them side by side before you commit.