Indented lines of code tokens type themselves in, chunk by chunk, behind a caret that blinks once the block is done. One stepped clip-path polygon does the typing, no per-line timers.
Also known as: code typing animation, code editor loader, terminal typing loader.
<div class="ldr-code-lines" role="status" aria-label="Loading">
<div class="ldr-code-lines-code">
<span><i class="ldr-code-lines-key" style="--w: 0.35"></i><i style="--w: 0.55"></i></span>
<span style="--in: 1"><i style="--w: 0.3"></i><i class="ldr-code-lines-key" style="--w: 0.25"></i><i style="--w: 0.3"></i></span>
<span style="--in: 2"><i style="--w: 0.5"></i><i class="ldr-code-lines-key" style="--w: 0.2"></i></span>
<span style="--in: 1"><i class="ldr-code-lines-key" style="--w: 0.3"></i><i style="--w: 0.45"></i></span>
<span><i style="--w: 0.2"></i></span>
</div>
</div>.ldr-code-lines {
--s: var(--ldr-size, 48px);
--c: var(--ldr-color, #3b82f6);
position: relative;
width: calc(var(--s) * 1.3);
height: var(--s);
}
.ldr-code-lines-code {
display: grid;
grid-template-rows: repeat(5, 1fr);
height: 100%;
animation: ldr-code-lines-type 4s steps(7, end) -1.3s infinite;
}
.ldr-code-lines-code span {
display: flex;
align-items: center;
gap: calc(var(--s) / 14);
padding-left: calc(var(--in, 0) * var(--s) / 5);
}
.ldr-code-lines-code i {
width: calc(var(--s) * var(--w));
height: calc(var(--s) / 10);
border-radius: var(--s);
background: color-mix(in srgb, var(--ldr-color, #3b82f6) 40%, transparent);
}
.ldr-code-lines-code .ldr-code-lines-key { background: var(--c); }
.ldr-code-lines::after {
content: "";
position: absolute;
top: 0;
left: 0;
width: calc(var(--s) / 16);
height: calc(var(--s) / 7);
border-radius: calc(var(--s) / 60);
background: var(--c);
animation: ldr-code-lines-caret 4s steps(7, end) -1.3s infinite;
}
@keyframes ldr-code-lines-type {
0%, 2% { clip-path: polygon(0 0, 100% 0, 100% 0, 0 0, 0 20%, 0 20%); opacity: 1; }
14% { clip-path: polygon(0 0, 100% 0, 100% 0, 76% 0, 76% 20%, 0 20%); }
14.01%, 17% { clip-path: polygon(0 0, 100% 0, 100% 20%, 15% 20%, 15% 40%, 0 40%); animation-timing-function: steps(8, end); }
30% { clip-path: polygon(0 0, 100% 0, 100% 20%, 93% 20%, 93% 40%, 0 40%); }
30.01%, 33% { clip-path: polygon(0 0, 100% 0, 100% 40%, 31% 40%, 31% 60%, 0 60%); animation-timing-function: steps(6, end); }
46% { clip-path: polygon(0 0, 100% 0, 100% 40%, 92% 40%, 92% 60%, 0 60%); }
46.01%, 49% { clip-path: polygon(0 0, 100% 0, 100% 60%, 15% 60%, 15% 80%, 0 80%); animation-timing-function: steps(7, end); }
60% { clip-path: polygon(0 0, 100% 0, 100% 60%, 80% 60%, 80% 80%, 0 80%); }
60.01%, 63% { clip-path: polygon(0 0, 100% 0, 100% 80%, 0 80%, 0 100%, 0 100%); animation-timing-function: steps(2, end); }
67%, 84% { clip-path: polygon(0 0, 100% 0, 100% 80%, 17% 80%, 17% 100%, 0 100%); opacity: 1; animation-timing-function: ease-in; }
94%, 100% { clip-path: polygon(0 0, 100% 0, 100% 80%, 17% 80%, 17% 100%, 0 100%); opacity: 0; }
}
@keyframes ldr-code-lines-caret {
0%, 2% { transform: translate(0, calc(var(--s) * 0.03)); opacity: 1; }
14% { transform: translate(calc(var(--s) * 0.99), calc(var(--s) * 0.03)); }
14.01%, 17% { transform: translate(calc(var(--s) * 0.2), calc(var(--s) * 0.23)); animation-timing-function: steps(8, end); }
30% { transform: translate(calc(var(--s) * 1.21), calc(var(--s) * 0.23)); }
30.01%, 33% { transform: translate(calc(var(--s) * 0.4), calc(var(--s) * 0.43)); animation-timing-function: steps(6, end); }
46% { transform: translate(calc(var(--s) * 1.2), calc(var(--s) * 0.43)); }
46.01%, 49% { transform: translate(calc(var(--s) * 0.2), calc(var(--s) * 0.63)); animation-timing-function: steps(7, end); }
60% { transform: translate(calc(var(--s) * 1.04), calc(var(--s) * 0.63)); }
60.01%, 63% { transform: translate(0, calc(var(--s) * 0.83)); animation-timing-function: steps(2, end); }
67% { transform: translate(calc(var(--s) * 0.22), calc(var(--s) * 0.83)); opacity: 1; animation-timing-function: step-end; }
71% { opacity: 0; animation-timing-function: step-end; }
75% { opacity: 1; animation-timing-function: step-end; }
79% { opacity: 0; animation-timing-function: step-end; }
83%, 84% { opacity: 1; animation-timing-function: ease-in; }
94%, 100% { transform: translate(calc(var(--s) * 0.22), calc(var(--s) * 0.83)); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.ldr-code-lines, .ldr-code-lines *, .ldr-code-lines::before, .ldr-code-lines::after, .ldr-code-lines *::before, .ldr-code-lines *::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.