Blog UX Patterns 7 min read

Blur Placeholder Routes for Soft Loading States

Use fallback.pics blur placeholder URLs to create smooth soft-focus loading states that prevent layout shift and feel intentional, not broken.

blur placeholder imageloading statesUX patternsimage fallbackCLS prevention
Blur Placeholder Routes for Soft Loading States

A solid gray box is technically a placeholder, but it reads as broken to users. Blur placeholders use a soft, out-of-focus aesthetic that communicates "image coming" rather than "image missing," which reduces abandonment on image-heavy pages.

The fallback.pics blur route generates deterministic blurred SVGs from a URL with no client-side CSS or JavaScript required. Drop the URL into an img src and the blur state renders immediately at any dimension.

Context

Why blur placeholders outperform solid color fills

Solid color placeholders hold layout space, but they create a hard visual discontinuity when the real image loads. The eye registers the color change as a flash rather than a reveal. Blur placeholders have no sharp edge to pop because the real image and the placeholder share the same general tonal range.

There is also a legibility benefit. A blurred shape signals an image is expected rather than absent, which matters for product grids, blog hero images, and media galleries where users scan before they wait.

The tradeoff is file size. An SVG blur effect is heavier than a flat rect element. For grids with dozens of tiles, measure whether the extra bytes per placeholder affect your First Contentful Paint before committing.

Basic URL

Add a blur placeholder with one URL

The blur route accepts any width and height. The output is an SVG that uses feGaussianBlur with a base rectangle tinted to a neutral mid-gray. The blur radius scales with the smaller dimension so small thumbnails and large heroes both look appropriately soft.

Use the URL directly in an img src or as a CSS background-image. No JavaScript, no build step, no base64 encoding required.

Implementation text
<!-- Basic blur placeholder at 800x500 -->
<img
  src="https://fallback.pics/api/v1/blur/800x500"
  width="800"
  height="500"
  alt="Loading product photo"
/>

<!-- Smaller thumbnail in a grid -->
<img
  src="https://fallback.pics/api/v1/blur/400x400"
  width="400"
  height="400"
  alt="Loading product image"
/>

Tinted blur

Match your brand with a tinted blur background

Pass a hex background color to shift the blur base from neutral gray to a brand-adjacent tone. Purple-tinted blur placeholders, for example, feel much more intentional on a purple-themed product page than generic gray.

The foreground color parameter controls the shimmer highlight layered inside the blur. Keeping it close to the background color produces a subtle effect; a higher contrast value gives a more active animated appearance.

Implementation text
<!-- Purple-tinted blur for a branded SaaS UI -->
https://fallback.pics/api/v1/blur/800x500/7C3AED/8B5CF6

<!-- Warm tint for a food or travel context -->
https://fallback.pics/api/v1/blur/800x500/F97316/FCD34D

<!-- Dark mode neutral blur -->
https://fallback.pics/api/v1/blur/800x500/27272A/3F3F46

React pattern

Swap blur for real image on load

The classic pattern: render the blur placeholder as the initial src, then swap in the real image URL once the asset loads. The transition is smooth because the blur and the real image share the same spatial footprint.

Use CSS opacity transition on the real image rather than instantly replacing the blur. A 200ms fade hides any resolution difference between the placeholder and the final asset.

Implementation tsx
import { useState } from 'react';

const BLUR_URL = 'https://fallback.pics/api/v1/blur/800x500';

function ProductImage({ src, alt }: { src: string; alt: string }) {
  const [loaded, setLoaded] = useState(false);

  return (
    <div style={{ position: 'relative', width: 800, height: 500 }}>
      <img
        src={BLUR_URL}
        width={800}
        height={500}
        alt=""
        aria-hidden="true"
        style={{ position: 'absolute', inset: 0 }}
      />
      <img
        src={src}
        width={800}
        height={500}
        alt={alt}
        onLoad={() => setLoaded(true)}
        onError={(e) => { e.currentTarget.src = BLUR_URL; }}
        style={{
          position: 'absolute',
          inset: 0,
          opacity: loaded ? 1 : 0,
          transition: 'opacity 200ms ease',
        }}
      />
    </div>
  );
}

CSS background

Use blur URLs as CSS background-image

CSS background-image works well for decorative containers where you control the box size through layout rather than the img element. Set background-size to cover to let the blur fill the container edge to edge.

This approach also works for inline styles in frameworks that accept style objects, and for server-rendered HTML where you want to avoid JavaScript altogether.

Implementation text
/* In a stylesheet */
.product-card__media {
  background-image: url('https://fallback.pics/api/v1/blur/600x400');
  background-size: cover;
  aspect-ratio: 3 / 2;
}

/* Inline in a React component */
<div
  style={{
    backgroundImage: "url('https://fallback.pics/api/v1/blur/600x400')",
    backgroundSize: 'cover',
    aspectRatio: '3 / 2',
  }}
/>

Next.js

Blur placeholders in Next.js Image

Next.js Image accepts blurDataURL as a base64 string, but external blur URLs work just as well with the standard img fallback pattern or a plain img element inside a Next.js page. For App Router with server components, fetch the blur URL string and pass it as the src before the real image resolves.

If you use next/image and want the built-in blur effect, you can still use fallback.pics for error states: the onError handler fires when the primary src fails, letting you swap in the blur URL as a controlled broken-image fallback.

Implementation tsx
// pages/ProductCard.tsx – error fallback with blur URL
'use client';
import Image from 'next/image';

const BLUR = 'https://fallback.pics/api/v1/blur/800x500';

export function ProductCard({ src, alt }: { src: string; alt: string }) {
  return (
    <Image
      src={src}
      alt={alt}
      width={800}
      height={500}
      onError={(e) => {
        e.currentTarget.src = BLUR;
      }}
    />
  );
}

Performance note

Cache and CDN behavior for blur SVGs

Blur SVGs are generated deterministically from the URL parameters. The same URL always returns the same bytes, so CDN caching is safe with a long max-age. Set Cache-Control: public, max-age=31536000, immutable for production workloads.

For grids with many identical-dimension tiles, browsers will cache the first blur response and reuse it for all subsequent img tags pointing to the same URL. One network round-trip covers the entire grid.

Implementation text
# Verify caching headers
curl -I "https://fallback.pics/api/v1/blur/400x400"

# Internal links for deeper reading
# https://fallback.pics/docs/
# https://fallback.pics/placeholder-image-api/
# https://fallback.pics/blog/skeleton-placeholder-images-vs-static-fallbacks/
# https://fallback.pics/blog/prevent-layout-shift-missing-images/

Key takeaways

What to standardize before shipping

  • Blur placeholders feel intentional; solid gray boxes feel broken.
  • The /blur/ route generates deterministic SVGs from a URL with no client JavaScript.
  • Tint the blur with hex color parameters to match your brand palette.
  • Fade from blur to real image with a CSS opacity transition, not an instant swap.
  • One blur URL is cached by the browser for all tiles sharing that dimension.

Production fallback layer

Use fallback.pics anywhere an image URL is accepted.

Start with one deterministic URL and standardize fallback behavior across your design system.

Read the docs