← Blog

react-brandicons: progressive icon loading with placeholders

· BrandIcons Team · react, component, guide

The problem

Ask BrandIcons for an icon and most of the time it’s already on the CDN — about 20ms in the browser from the nearest edge. But the first time anyone in the world requests an icon for a brand-new domain, we have to go find it. Discovery takes a moment — sometimes well under a second, sometimes a few seconds if we have to research the brand.

A plain <img> doesn’t handle this gracefully. It either flashes a broken-image glyph or sits blank until the bytes arrive. Neither is what we want next to your beautifully-aligned logo grid.

We wanted a component that quietly shows a placeholder during discovery and quietly upgrades to the real icon when it lands — no reload, no layout shift, no work for you.

What the component does

react-brandicons wraps an <img> and does two things:

  1. Renders the icon URL with your chosen placeholder and loadingPlaceholder baked into the query string. If the icon is already cached, you just see it. If not, the backend serves your loading placeholder immediately.
  2. Polls the x-brandicons-state header on a HEAD request to the same URL. When the icon arrives in S3, the component swaps in the real image — no reload needed.

The HEAD requests share the CloudFront cache key with the GET, so once the icon is cached most polls hit the edge — cheap, fast, and quota-friendly.

Twelve built-in placeholders

Every account ships with twelve system placeholders. Reference them with an @ prefix:

  • @globe — generic web/site
  • @image — generic image
  • @image-off — image broken / not found
  • @building — company / organization
  • @user — person / profile
  • @package — product / app
  • @file — document
  • @link — URL / link
  • @help — unknown / help
  • @loader — loading (static)
  • @loader-spin — loading (animated SVG)
  • @dot — minimal neutral

Uploaded a custom placeholder? Reference it by its plain name (no @): placeholder="my-logo".

Minimal example

bun add react-brandicons
# or: npm i react-brandicons
import { BrandIcon } from "react-brandicons";

export function CompanyLogo() {
    return <BrandIcon domain="github.com" apiKey="YOUR_API_KEY" size="medium" />;
}

That’s it. With no placeholders set you’ll get a broken-image flash on uncached domains — fine for known-good cases like your own logo, less great for user-supplied domains.

Advanced example

For the real-world case (user-typed domains, lazy lists, anything you don’t pre-warm) you want both a loading placeholder and a not-found placeholder:

import { BrandIcon } from "react-brandicons";

export function VendorRow({ domain }: { domain: string }) {
    return (
        <BrandIcon
            domain={domain}
            apiKey="YOUR_API_KEY"
            size="large"
            loadingPlaceholder="@loader-spin"
            placeholder="@image-off"
        />
    );
}

A few things worth knowing:

  • size accepts ico, small, medium, and large (IconSize is exported for type-safe lists).
  • loadingPlaceholder is shown while the worker is still searching (x-brandicons-state: loading).
  • placeholder is the terminal fallback — shown when discovery gives up.

Under the hood

After the initial load the component issues HEAD requests against the same URL and reads x-brandicons-state. As long as the state is loading, it keeps checking with backoff. When the header disappears (real icon is in S3) or the state turns terminal, polling stops. The component never spams the server.

Try it

We use react-brandicons ourselves on the marketing site you’re reading right now — every brand logo on the page goes through it.

If you don’t have an API key yet, grab one — the Community plan is free forever and includes 500K requests per month.