cd ../writing
// css craft · production-grade

CSS loaders that don't embarrass you.

Search "css loader" on Google and brace yourself. 90% of the results are spinning rainbow gradients with comic sans accessibility. Here are 10 loaders the actually-good companies ship — Stripe, Linear, Apple, Vercel. Pure CSS, accessible, and not made for a 2008 Geocities site.

10 loaders ~20 lines each reduced-motion safe © use freely

What's wrong with CodePen loaders

  • Spin so fast they're physically nauseating
  • Use 12 colors when the brand is 2
  • Are 4× bigger than they should be on mobile
  • Don't respect prefers-reduced-motion
  • Use 80 lines of CSS for what should be 8
  • Have animations that "look cool" but communicate nothing — no sense of progress, scale, or urgency

A good loader does one job: tell the user something is happening, without distracting them. Bonus points if it gives a sense of how long. Bonus points if it matches the brand surface.

The ones below are organized by use case. Read the labels — a 3-dot loader works for inline status text but is wrong for a full-page boot. A skeleton shimmer is right for content placeholders but wrong for "saving...". Match the tool to the job.

01 / inline status

The Stripe 3-dot

Three opacity-pulsing dots, staggered by 160ms. Reads as "thinking" without being aggressive. The default for inline status text ("processing…", "loading…").

inlinesubtle
02 / top-of-page

Linear-style indeterminate bar

A thin colored bar sliding across a track. Used at the top of pages or sections during navigation. The eased timing prevents the "regular swipe" rhythm — feels natural.

progresstopbar
03 / iOS-style

Apple breathing dots

Three dots scaling from 0.4 to 1.0 with a 200ms stagger. Familiar to iOS users. Heavier visual weight than Stripe's — use when you want the loader to be the focus.

centerios
04 / brand placeholder

Vercel triangle pulse

Brand mark (triangle here, anything for you) breathing in opacity and scale. Use when the brand should reinforce the wait — boot screens, splash, full-page load.

brandboot
05 / classic spinner

The classic conic spinner

Replacement for the border-hack spinner. Uses conic-gradient for a smooth sweep from transparent to opaque. Mask creates the ring. Better antialiasing than borders.

centeruniversal
06 / content placeholder

Skeleton shimmer

For loading content blocks (cards, list items, articles). A diagonal shimmer sweeps across a placeholder shape every 1.6s. Doesn't say "wait" — says "this content is coming".

skeletonblock
07 / playful

Bouncing wave dots

Five dots bouncing in a wave, each a different brand color. Use when the brand allows playfulness — onboarding, success states, fun apps. Avoid for serious financial or legal contexts.

playfulbrand
08 / terminal aesthetic

The blinking caret

One block character, step-blinking at 550ms. The whole loader. Used when typography is the brand (terminal UIs, dev tools, CLI-style products).

textterminal
loading
09 / nostalgic / niche

Pacman eating pellets

Pure novelty. Don't use it in production unless your brand is gaming or retro. Including it because it's an interesting demo of clip-path animation. Pellets disappear into the mouth.

novelty
10 / abstract

Morphing square

Square ↔ circle ↔ square, rotating 180° along the way. Subtle hypnotic quality. Pair with a gradient fill so it doesn't read as solid.

abstractcenter

One last thing.

Wrap every loader in this accessibility rule. It's two lines, it costs you nothing, and it's the difference between "respects users" and "causes nausea in motion-sensitive people":

@media (prefers-reduced-motion: reduce) {
  .spinner, .dots i, .wave i {
    animation: none;
  }
}

Or, depending on the loader, you can substitute a static state (e.g., the spinner pauses on its 12-o'clock position rather than disappearing). The point is to not assume every user can or wants to see motion.