AI CSS Animations

Card Stack Loader

Three cards stacked in perspective: the front card swipes away, the others step forward, and it slides back in at the rear. translateZ supplies the depth, so cards shrink with distance for free.

Also known as: stacked cards loader, swipe cards animation, card deck loader.

HTML

<div class="ldr-card-stack" role="status" aria-label="Loading">
  <span></span><span></span><span></span>
</div>

CSS

.ldr-card-stack {
  --s: var(--ldr-size, 48px);
  --c: var(--ldr-color, #3b82f6);
  position: relative;
  width: var(--s);
  height: var(--s);
  perspective: calc(var(--s) * 3);
  transform-style: preserve-3d;
}
.ldr-card-stack span {
  position: absolute;
  left: calc(50% - var(--s) * 0.46);
  bottom: calc(var(--s) * 0.14);
  width: calc(var(--s) * 0.92);
  height: calc(var(--s) * 0.6);
  border-radius: calc(var(--s) / 12);
  background-color: var(--c);
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.6) 0 0),
    linear-gradient(rgba(255, 255, 255, 0.35) 0 0);
  background-size: 42% 11%, 64% 9%;
  background-position: 16% 32%, 16% 66%;
  background-repeat: no-repeat;
  animation: ldr-card-stack-cycle 3s cubic-bezier(0.65, 0, 0.35, 1) infinite;
}
.ldr-card-stack span:nth-child(2) { animation-delay: -2s; }
.ldr-card-stack span:nth-child(3) { animation-delay: -1s; }
@keyframes ldr-card-stack-cycle {
  0%, 18% {
    transform: translate3d(0, 0, 0) rotate(0deg);
    opacity: 1;
    background-color: var(--c);
    animation-timing-function: cubic-bezier(0.5, 0, 0.75, 0);
  }
  28% {
    transform: translate3d(calc(var(--s) * 0.9), calc(var(--s) * 0.04), 0) rotate(18deg);
    opacity: 0;
    background-color: var(--c);
  }
  29% {
    transform: translate3d(0, calc(var(--s) * -0.28), calc(var(--s) * -0.6)) rotate(0deg);
    opacity: 0;
    background-color: color-mix(in srgb, var(--c) 40%, #fff);
  }
  38%, 51% {
    transform: translate3d(0, calc(var(--s) * -0.28), calc(var(--s) * -0.6)) rotate(0deg);
    opacity: 1;
    background-color: color-mix(in srgb, var(--c) 40%, #fff);
  }
  61%, 85% {
    transform: translate3d(0, calc(var(--s) * -0.14), calc(var(--s) * -0.3)) rotate(0deg);
    opacity: 1;
    background-color: color-mix(in srgb, var(--c) 68%, #fff);
  }
  95%, 100% {
    transform: translate3d(0, 0, 0) rotate(0deg);
    opacity: 1;
    background-color: var(--c);
  }
}

@media (prefers-reduced-motion: reduce) {
  .ldr-card-stack, .ldr-card-stack *, .ldr-card-stack::before, .ldr-card-stack::after, .ldr-card-stack *::before, .ldr-card-stack *::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