About this snippet

Six loaders for the moments when an AI feature is working, each built from one element plus its pseudo-elements or small child elements. Spark spin turns and shrinks a four-point star. Gradient ring spins a conic gradient with a fading tail. Neural grid lights a three-by-three grid of dots in a diagonal wave. Orbit sends two dots around a center, Token stream grows bars from left to right like words arriving, and Word cycle rolls through Thinking, Searching and Writing.

The small line under each loader's name is the technique that drives it. The star is a clip-path polygon filled with a conic gradient. The ring is a conic gradient cut into a hoop by a radial-gradient mask. The grid wave comes from animation-delay steps set with nth-of-type, and the word cycle moves a column of words with translateY inside a box that is one line tall.

Every loader is a status region with visually hidden Loading text, so screen readers have words for the animation. For the text to be announced, keep the region in the page and add the text when work starts. The four accent colors are custom properties at the top of the stylesheet, and a dark color scheme swaps the tile and page colors. For visitors who prefer reduced motion, the movement stops and each loader fades gently instead, and in forced colors mode the loaders keep their own colors so they stay visible.

Size
8.2 KB
Uses
HTML, CSS
Loads
Nothing external
Views
79
<main class="loaders">
  <h1 class="sr-only">AI loading animations</h1>
  <section class="tile">
    <div class="loader loader-spark" role="status"><span class="sr-only">Loading</span></div>
    <h2 class="tile-name">Spark spin</h2>
    <p class="tile-tech">clip-path</p>
  </section>
  <section class="tile">
    <div class="loader loader-ring" role="status"><span class="sr-only">Loading</span></div>
    <h2 class="tile-name">Gradient ring</h2>
    <p class="tile-tech">conic-gradient, mask</p>
  </section>
  <section class="tile">
    <div class="loader loader-grid" role="status">
      <span class="sr-only">Loading</span>
      <i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i>
    </div>
    <h2 class="tile-name">Neural grid</h2>
    <p class="tile-tech">animation-delay</p>
  </section>
  <section class="tile">
    <div class="loader loader-orbit" role="status"><span class="sr-only">Loading</span><i></i></div>
    <h2 class="tile-name">Orbit</h2>
    <p class="tile-tech">rotate</p>
  </section>
  <section class="tile">
    <div class="loader loader-tokens" role="status">
      <span class="sr-only">Loading</span>
      <i></i><i></i><i></i><i></i><i></i>
    </div>
    <h2 class="tile-name">Token stream</h2>
    <p class="tile-tech">scaleX</p>
  </section>
  <section class="tile">
    <div class="loader loader-words" role="status">
      <span class="sr-only">Loading</span>
      <span class="words" aria-hidden="true">
        <span>Thinking</span>
        <span>Searching</span>
        <span>Writing</span>
        <span>Thinking</span>
      </span>
    </div>
    <h2 class="tile-name">Word cycle</h2>
    <p class="tile-tech">translateY</p>
  </section>
</main>
*, *::before, *::after {
  box-sizing: border-box;
}

:root {
  --ai-blue: #3d7bfa;
  --ai-violet: #9a5cf6;
  --ai-pink: #ef4f98;
  --ai-amber: #f7a531;
  --page: #eef2f7;
  --tile: #ffffff;
  --text: #0f172a;
  --muted: #64748b;
  --track: #e2e8f0;
}

body {
  font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  margin: 0;
  background: var(--page);
  color: var(--text);
}

.loaders {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  align-content: center;
  gap: 16px;
  max-width: 780px;
  min-height: 100vh;
  margin: 0 auto;
  padding: 32px 20px;
}

.tile {
  display: grid;
  grid-template-rows: 72px auto auto;
  justify-items: center;
  align-content: start;
  gap: 4px;
  padding: 24px 16px 22px;
  border-radius: 16px;
  background: var(--tile);
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06);
  text-align: center;
}

.tile .loader {
  align-self: center;
}

.tile-name {
  margin: 14px 0 0;
  font-size: 0.9375rem;
  font-weight: 600;
}

.tile-tech {
  margin: 0;
  font-family: ui-monospace, Consolas, monospace;
  font-size: 0.8125rem;
  color: var(--muted);
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.loader {
  position: relative;
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
}

.loader-spark::before,
.loader-spark::after {
  content: "";
  position: absolute;
  background: conic-gradient(var(--ai-blue), var(--ai-violet), var(--ai-pink), var(--ai-amber), var(--ai-blue));
  clip-path: polygon(50% 0%, 58% 42%, 100% 50%, 58% 58%, 50% 100%, 42% 58%, 0% 50%, 42% 42%);
}

.loader-spark::before {
  inset: 8px 12px 12px 8px;
  animation: spark-turn 1.6s ease-in-out infinite;
}

.loader-spark::after {
  top: 2px;
  right: 2px;
  width: 18px;
  height: 18px;
  animation: spark-blink 1.6s ease-in-out -0.8s infinite;
}

.loader-ring {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: conic-gradient(rgba(61, 123, 250, 0), var(--ai-blue) 30%, var(--ai-violet) 60%, var(--ai-pink) 85%, var(--ai-amber));
  -webkit-mask: radial-gradient(farthest-side, transparent 78%, #000 81%);
  mask: radial-gradient(farthest-side, transparent 78%, #000 81%);
  animation: spin 1s linear infinite;
}

.loader-grid {
  grid-template-columns: repeat(3, 12px);
  gap: 8px;
  place-content: center;
}

.loader-grid i {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--dot, var(--ai-blue));
  opacity: 0.2;
  animation: grid-wave 1.4s ease-in-out infinite;
  animation-delay: calc(var(--step, 0) * 0.14s);
}

.loader-grid i:nth-of-type(2),
.loader-grid i:nth-of-type(4) {
  --step: 1;
}

.loader-grid i:nth-of-type(3),
.loader-grid i:nth-of-type(5),
.loader-grid i:nth-of-type(7) {
  --step: 2;
  --dot: var(--ai-violet);
}

.loader-grid i:nth-of-type(6),
.loader-grid i:nth-of-type(8) {
  --step: 3;
  --dot: var(--ai-pink);
}

.loader-grid i:nth-of-type(9) {
  --step: 4;
  --dot: var(--ai-amber);
}

.loader-orbit {
  width: 60px;
  height: 60px;
  border: 2px solid var(--track);
  border-radius: 50%;
}

.loader-orbit::before {
  content: "";
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--ai-blue), var(--ai-violet));
}

.loader-orbit::after,
.loader-orbit i {
  position: absolute;
  border-radius: 50%;
  animation: spin 1.2s linear infinite;
}

.loader-orbit::after {
  content: "";
  inset: -2px;
  background: radial-gradient(circle at 50% 5px, var(--ai-pink) 4px, transparent 5px);
}

.loader-orbit i {
  inset: 10px;
  background: radial-gradient(circle at 50% 3px, var(--ai-amber) 3px, transparent 4px);
  animation-duration: 0.8s;
  animation-direction: reverse;
}

.loader-tokens {
  display: flex;
  gap: 6px;
  width: auto;
}

.loader-tokens i {
  height: 10px;
  border-radius: 5px;
  background: var(--ai-blue);
  transform-origin: left center;
  animation: token 1.8s ease-out infinite;
}

.loader-tokens i:nth-of-type(1) {
  width: 20px;
}

.loader-tokens i:nth-of-type(2) {
  width: 34px;
  background: var(--ai-violet);
  animation-delay: 0.15s;
}

.loader-tokens i:nth-of-type(3) {
  width: 14px;
  background: var(--ai-violet);
  animation-delay: 0.3s;
}

.loader-tokens i:nth-of-type(4) {
  width: 26px;
  background: var(--ai-pink);
  animation-delay: 0.45s;
}

.loader-tokens i:nth-of-type(5) {
  width: 18px;
  background: var(--ai-amber);
  animation-delay: 0.6s;
}

.loader-words {
  width: auto;
  height: 1.5em;
  overflow: hidden;
  place-items: start center;
  font-size: 1.125rem;
  font-weight: 600;
  line-height: 1.5em;
}

.words {
  display: grid;
  animation: words 4.5s cubic-bezier(0.65, 0, 0.35, 1) infinite;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

@keyframes spark-turn {
  0% {
    transform: rotate(0deg) scale(1);
  }
  50% {
    transform: rotate(90deg) scale(0.75);
  }
  100% {
    transform: rotate(180deg) scale(1);
  }
}

@keyframes spark-blink {
  0%, 100% {
    transform: scale(1);
    opacity: 1;
  }
  50% {
    transform: scale(0.3);
    opacity: 0.4;
  }
}

@keyframes grid-wave {
  0%, 60%, 100% {
    opacity: 0.2;
    transform: scale(0.7);
  }
  30% {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes token {
  0% {
    transform: scaleX(0);
    opacity: 1;
  }
  20%, 70% {
    transform: scaleX(1);
    opacity: 1;
  }
  90%, 100% {
    transform: scaleX(1);
    opacity: 0;
  }
}

@keyframes words {
  0%, 28% {
    transform: translateY(0);
  }
  33%, 61% {
    transform: translateY(-1.5em);
  }
  66%, 94% {
    transform: translateY(-3em);
  }
  100% {
    transform: translateY(-4.5em);
  }
}

@keyframes pulse {
  50% {
    opacity: 0.45;
  }
}

@media (min-width: 1000px) {
  .loaders {
    max-width: 880px;
  }

  .tile {
    grid-template-rows: 90px auto auto;
    padding: 32px 16px 28px;
  }

  .tile .loader {
    scale: 1.25;
  }
}

@media (max-width: 600px) {
  .loaders {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 360px) {
  .loaders {
    gap: 10px;
    padding: 20px 12px;
  }

  .tile {
    padding: 20px 8px 16px;
  }
}

@media (prefers-color-scheme: dark) {
  :root {
    --page: #0b1120;
    --tile: #151e30;
    --text: #e2e8f0;
    --muted: #94a3b8;
    --track: #334155;
  }
}

@media (forced-colors: active) {
  .loader,
  .loader::before,
  .loader::after,
  .loader i {
    forced-color-adjust: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .loader::before,
  .loader::after,
  .loader i,
  .loader .words {
    animation: none;
  }

  .loader-grid i {
    opacity: 0.7;
  }

  .loader {
    animation: pulse 2s ease-in-out infinite;
  }
}
Esc
↑↓ moveEnter select

Start from a template

Open an .html file, or HTML, CSS, JS, Markdown, Less or TypeScript files. You can also drop them here. Styles and scripts go into their panes.

Share

Source

Keyboard shortcuts

Save