Pure CSS Loaders & patterns #loop #sass-loop

Tile wave in pure CSS

A 4×4 grid of two-sided tiles flipping in a diagonal wave.

  • 37 lines of CSS
  • 8 lines of HTML
  • No JavaScript
  • No dependencies
  • MIT licensed

How it works

  1. Each tile has two faces from ::before and ::after, back to back, both with backface-visibility: hidden.
  2. Every tile runs the same flip animation.
  3. The wave comes purely from animation-delay = (row + column) × step. In plain CSS you set --d per tile; in Sass a nested @for loop writes it.

Key techniques

  • backface-visibility
  • delay = (row + column) × step
  • Sass nested @for

Copy-paste code

HTML 8 lines

<div class="scene">
  <div class="tiles">
    <i style="--d:0"></i><i style="--d:1"></i><i style="--d:2"></i><i style="--d:3"></i>
    <i style="--d:1"></i><i style="--d:2"></i><i style="--d:3"></i><i style="--d:4"></i>
    <i style="--d:2"></i><i style="--d:3"></i><i style="--d:4"></i><i style="--d:5"></i>
    <i style="--d:3"></i><i style="--d:4"></i><i style="--d:5"></i><i style="--d:6"></i>
  </div>
</div>

CSS 37 lines

.scene {
  perspective: 800px;
}

.tiles {
  display: grid;
  grid-template-columns: repeat(4, 48px);
  gap: 8px;
  transform-style: preserve-3d;
  transform: rotateX(38deg) rotateZ(-8deg);
}

.tiles i {
  position: relative;
  height: 48px;
  transform-style: preserve-3d;
  animation: tile-flip 3.2s ease-in-out infinite;
  animation-delay: calc(var(--d) * 0.13s);   /* --d = row + column */
}

.tiles i::before,
.tiles i::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 8px;
  backface-visibility: hidden;
}

.tiles i::before { background: #8b6cff; }
.tiles i::after  { background: #2ee6d6; transform: rotateY(180deg); }

@keyframes tile-flip {
  0%, 15%   { transform: rotateY(0deg); }
  45%, 65%  { transform: rotateY(180deg) translateZ(-14px); }
  95%, 100% { transform: rotateY(360deg); }
}

Sass source 61 lines

$cols: 4;

.d-tiles {
  display: grid;
  grid-template-columns: repeat($cols, 38px);
  gap: 6px;
  transform-style: preserve-3d;
  transform: rotateX(38deg) rotateZ(-8deg);

  i {
    position: relative;
    height: 38px;
    transform-style: preserve-3d;
    animation: d-tiles-flip 3.2s ease-in-out infinite;

    // Two faces back to back; each hides when it turns away.
    &::before,
    &::after {
      content: '';
      position: absolute;
      inset: 0;
      border-radius: 8px;
      backface-visibility: hidden;
    }

    &::before {
      background: var(--accent);
    }

    &::after {
      background: var(--accent-2);
      transform: rotateY(180deg);
    }
  }

  // delay = (row + column) × step → a diagonal wave
  @for $row from 0 to $cols {
    @for $col from 0 to $cols {
      i:nth-child(#{$row * $cols + $col + 1}) {
        animation-delay: ($row + $col) * 0.13s;
      }
    }
  }
}

@keyframes d-tiles-flip {
  0%,
  15% {
    transform: rotateY(0deg);
  }

  45%,
  65% {
    transform: rotateY(180deg) translateZ(-14px);
  }

  95%,
  100% {
    transform: rotateY(360deg);
  }
}
Standalone snippet · plain CSS, no build step, no dependencies.