Snail

No dependencies
Code format
React + Tailwind
const css = `
  /* The foot stretches forward from the tail, then gathers up behind the head. */
  @keyframes loader-snail-stretch { 0%, 100% { transform: scaleX(1); } 45% { transform: scaleX(1.08); } }
  @keyframes loader-snail-rock { 0%, 100% { transform: translate(0, 0) rotate(0deg); } 45% { transform: translate(2.5px, -1px) rotate(-3deg); } }
  @keyframes loader-snail-stalk { 0%, 100% { transform: rotate(-6deg); } 50% { transform: rotate(8deg); } }
  /* The ground slides back under it, so it crawls without ever leaving its spot. */
  @keyframes loader-snail-ground { to { transform: translateX(-40px); } }
  @keyframes loader-snail-glint { from { stroke-dashoffset: 0; } to { stroke-dashoffset: 40; } }
  .loader-snail { position: relative; width: 150px; height: 90px; }
  .loader-snail svg { width: 100%; height: 100%; overflow: visible; }
  .loader-snail__foot { transform-origin: 30px 60px; animation: loader-snail-stretch 1.8s ease-in-out infinite; }
  .loader-snail__skin { fill: light-dark(#5f626a, #a7abb2); }
  .loader-snail__shell { transform-origin: 48px 54px; animation: loader-snail-rock 1.8s ease-in-out infinite; }
  .loader-snail__whorl { fill: light-dark(#cbced3, #393c41); stroke: light-dark(#50535b, #c6c8cd); stroke-width: 1.4; }
  .loader-snail__spiral { fill: none; stroke: light-dark(#50535b, #c6c8cd); stroke-width: 1.4; stroke-linecap: round; }
  .loader-snail__stalk { animation: loader-snail-stalk 2.2s ease-in-out infinite; }
  .loader-snail__stalk--near { transform-origin: 86px 43px; }
  .loader-snail__stalk--far { transform-origin: 90px 44px; animation-delay: -0.8s; }
  .loader-snail__stem { fill: none; stroke: light-dark(#5f626a, #a7abb2); stroke-width: 2; stroke-linecap: round; }
  .loader-snail__stalk--far .loader-snail__stem { stroke: light-dark(#6d7179, #7d8087); }
  .loader-snail__eye { fill: light-dark(#42454c, #e5e6e9); }
  .loader-snail__pupil { fill: light-dark(#fbfcfd, #121417); }
  .loader-snail__ground { stroke: light-dark(#cbced3, #393c41); stroke-width: 1.5; stroke-linecap: round; }
  .loader-snail__pebbles { animation: loader-snail-ground 6s linear infinite; }
  .loader-snail__pebble { fill: light-dark(#cbced3, #393c41); }
  .loader-snail__trail { fill: none; stroke: light-dark(#6d7179, #7d8087); stroke-width: 2.2; stroke-linecap: round; opacity: 0.6; }
  .loader-snail__glint { fill: none; stroke: light-dark(#42454c, #e5e6e9); stroke-width: 1.2; stroke-linecap: round; stroke-dasharray: 3 37; animation: loader-snail-glint 6s linear infinite; }
  @media (prefers-reduced-motion: reduce) {
    .loader-snail__foot, .loader-snail__shell, .loader-snail__stalk, .loader-snail__pebbles, .loader-snail__glint { animation: none; }
  }
`;

const PEBBLES = [6, 19, 31].flatMap((x) => [x, x + 40, x + 80, x + 120]);

export function SnailLoader() {
  return (
    <>
      <style>
        {css}
      </style>
      <div className="loader-snail" role="status" aria-label="Loading">
        <svg viewBox="0 0 120 72" aria-hidden="true">
          <g className="loader-snail__pebbles">
            {PEBBLES.map((x, i) => (
              <ellipse key={x} className="loader-snail__pebble" cx={x} cy={64 + (i % 3)} rx={1.4 + (i % 2)} ry="1" />
            ))}
          </g>
          <path className="loader-snail__ground" d="M0 61H120" />
          <path className="loader-snail__trail" d="M0 60H32" />
          <path className="loader-snail__glint" d="M0 59.6H32" />
          <g className="loader-snail__foot">
            <g className="loader-snail__stalk loader-snail__stalk--far">
              <path className="loader-snail__stem" d="M90 44Q91 36 95 31" />
              <circle className="loader-snail__eye" cx="95" cy="30" r="2" />
            </g>
            <path className="loader-snail__skin" d="M28 61Q30 55 38 54H74Q80 54 82 48Q84 41 89 41Q95 41 96 47Q97 54 90 58L88 61Z" />
            <g className="loader-snail__stalk loader-snail__stalk--near">
              <path className="loader-snail__stem" d="M86 43Q84 35 80 30" />
              <circle className="loader-snail__eye" cx="80" cy="29" r="2.2" />
              <circle className="loader-snail__pupil" cx="80.6" cy="29.2" r="0.9" />
            </g>
          </g>
          <g className="loader-snail__shell">
            <circle className="loader-snail__whorl" cx="50" cy="40" r="15" />
            {/* A spiral from half-circles about two alternating centres. */}
            <path className="loader-snail__spiral" d="M48 40a2 2 0 0 1 4 0a4 4 0 0 1 -8 0a6 6 0 0 1 12 0a8 8 0 0 1 -16 0a10 10 0 0 1 20 0" />
          </g>
        </svg>
      </div>
    </>
  );
}

Free to use in any project, commercial included. MIT-0, no attribution required. Licence

Install with shadcn

npx shadcn@latest add @movin/loader-snail
Source on GitHub

About this component

A snail crawling along: its foot stretches and gathers, the shell rocks, the eye stalks sway and a slime trail glistens behind it.

Dependencies
None
Difficulty
Beginner
Triggers
continuous, loop
Techniques
keyframes, svg, transform only
Reduced motion
Respected
Theme
Light and dark (follows your page's color-scheme)

The root has role="status" and aria-label="Loading"; everything inside is decorative. Under prefers-reduced-motion the snail is still.

More loaders

See all
Toaster
Wiper
Yo-yo
Ampersand