AI CSS Animations

Code Lines Loader

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.

HTML

<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>

CSS

.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;
  }
}

Customizing this loader

  • Color: set --ldr-color on the element or a parent. Currently previewing #3b82f6.
  • Size: set --ldr-size. Every dimension in the snippet is derived from it. Currently previewing 64px.
  • Speed: change the duration on the animation shorthand. Delays on the children are relative, so they keep their rhythm.
  • Accessibility: the root carries 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.

Similar loaders