Google's indeterminate linear indicator: two segments chase across the track, each stretching and shrinking on the Material easing curves. Transform only, so it stays smooth.
Also known as: material indeterminate progress bar, android linear progress indicator, google loading bar.
<div class="ldr-material-linear-progress" role="progressbar" aria-label="Loading"> <span class="ldr-material-linear-progress-primary"><span></span></span> <span class="ldr-material-linear-progress-secondary"><span></span></span> </div>
.ldr-material-linear-progress {
--s: var(--ldr-size, 48px);
--c: var(--ldr-color, #3b82f6);
position: relative;
width: calc(var(--s) * 6);
height: calc(var(--s) / 10);
overflow: hidden;
background: color-mix(in srgb, var(--ldr-color, #3b82f6) 22%, transparent);
}
.ldr-material-linear-progress > span {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
.ldr-material-linear-progress > span > span {
position: absolute;
inset: 0;
background: var(--c);
}
.ldr-material-linear-progress-primary {
left: -145.166611%;
animation: ldr-material-linear-progress-primary-move 2s linear infinite;
}
.ldr-material-linear-progress-primary > span {
animation: ldr-material-linear-progress-primary-scale 2s linear infinite;
}
.ldr-material-linear-progress-secondary {
left: -54.888891%;
animation: ldr-material-linear-progress-secondary-move 2s linear infinite;
}
.ldr-material-linear-progress-secondary > span {
animation: ldr-material-linear-progress-secondary-scale 2s linear infinite;
}
@keyframes ldr-material-linear-progress-primary-move {
0% { transform: translateX(0); }
20% { transform: translateX(0); animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819); }
59.15% { transform: translateX(83.67142%); animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352); }
100% { transform: translateX(200.611057%); }
}
@keyframes ldr-material-linear-progress-primary-scale {
0% { transform: scaleX(0.08); }
36.65% { transform: scaleX(0.08); animation-timing-function: cubic-bezier(0.334731, 0.12482, 0.785844, 1); }
69.15% { transform: scaleX(0.661479); animation-timing-function: cubic-bezier(0.06, 0.11, 0.6, 1); }
100% { transform: scaleX(0.08); }
}
@keyframes ldr-material-linear-progress-secondary-move {
0% { transform: translateX(0); animation-timing-function: cubic-bezier(0.15, 0, 0.515058, 0.409685); }
25% { transform: translateX(37.651913%); animation-timing-function: cubic-bezier(0.31033, 0.284058, 0.8, 0.733712); }
48.35% { transform: translateX(84.386165%); animation-timing-function: cubic-bezier(0.4, 0.627035, 0.6, 0.902026); }
100% { transform: translateX(160.277782%); }
}
@keyframes ldr-material-linear-progress-secondary-scale {
0% { transform: scaleX(0.08); animation-timing-function: cubic-bezier(0.205028, 0.057051, 0.57661, 0.453971); }
19.15% { transform: scaleX(0.457104); animation-timing-function: cubic-bezier(0.152313, 0.196432, 0.648374, 1.004315); }
44.15% { transform: scaleX(0.72796); animation-timing-function: cubic-bezier(0.257759, -0.003163, 0.211762, 1.38179); }
100% { transform: scaleX(0.08); }
}
@media (prefers-reduced-motion: reduce) {
.ldr-material-linear-progress, .ldr-material-linear-progress *, .ldr-material-linear-progress::before, .ldr-material-linear-progress::after, .ldr-material-linear-progress *::before, .ldr-material-linear-progress *::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.