A speedometer arc revs up with an overshoot, holds near the red line, then drops back to zero. The needle and the fill share one timing curve, so they always agree on where the value is.
Also known as: speedometer loading animation, meter loader, dial loading animation.
<svg class="ldr-gauge-loader" viewBox="0 0 48 48" role="status" aria-label="Loading"> <path class="ldr-gauge-loader-track" d="M8.4 37A18 18 0 1 1 39.6 37" pathLength="100"></path> <path class="ldr-gauge-loader-fill" d="M8.4 37A18 18 0 1 1 39.6 37" pathLength="100"></path> <path class="ldr-gauge-loader-needle" d="M24 28V15"></path> <circle class="ldr-gauge-loader-hub" cx="24" cy="28" r="3"></circle> </svg>
.ldr-gauge-loader {
--s: var(--ldr-size, 48px);
--c: var(--ldr-color, #3b82f6);
width: var(--s);
height: var(--s);
fill: none;
stroke-linecap: round;
}
.ldr-gauge-loader-track,
.ldr-gauge-loader-fill {
stroke-width: 5;
}
.ldr-gauge-loader-track {
stroke: color-mix(in srgb, var(--ldr-color, #3b82f6) 18%, transparent);
}
.ldr-gauge-loader-fill {
stroke: var(--c);
stroke-dasharray: 100 110;
animation: ldr-gauge-loader-fill 2.4s infinite;
}
/* The hub sits at 24,28 of the 48 box, which is 50% 58.33%. */
.ldr-gauge-loader-needle {
stroke: var(--c);
stroke-width: 3;
transform-origin: 50% 58.33%;
animation: ldr-gauge-loader-needle 2.4s infinite;
}
.ldr-gauge-loader-hub {
fill: var(--c);
}
@keyframes ldr-gauge-loader-fill {
0% { stroke-dashoffset: 101; animation-timing-function: cubic-bezier(0.34, 1.3, 0.64, 1); }
45%, 60% { stroke-dashoffset: 12; animation-timing-function: cubic-bezier(0.6, 0, 0.4, 1); }
92%, 100% { stroke-dashoffset: 101; }
}
@keyframes ldr-gauge-loader-needle {
0% { transform: rotate(-122deg); animation-timing-function: cubic-bezier(0.34, 1.3, 0.64, 1); }
45%, 60% { transform: rotate(91deg); animation-timing-function: cubic-bezier(0.6, 0, 0.4, 1); }
92%, 100% { transform: rotate(-122deg); }
}
@media (prefers-reduced-motion: reduce) {
.ldr-gauge-loader, .ldr-gauge-loader *, .ldr-gauge-loader::before, .ldr-gauge-loader::after, .ldr-gauge-loader *::before, .ldr-gauge-loader *::after {
animation-duration: 3s;
}
}--ldr-color on the element or a parent.
Currently previewing #3b82f6.--ldr-size. Every dimension in the snippet is
derived from it. Currently previewing 64px.animation shorthand.
Delays on the children are relative, so they keep their rhythm.role="status" and an
aria-label so screen readers announce the wait. The reduced-motion rule
at the end slows the animation for users who asked for less motion.