react-brandicons: progressive icon loading with placeholders
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:
- Renders the icon URL with your chosen
placeholderandloadingPlaceholderbaked into the query string. If the icon is already cached, you just see it. If not, the backend serves your loading placeholder immediately. - Polls the
x-brandicons-stateheader 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:
sizeacceptsico,small,medium, andlarge(IconSizeis exported for type-safe lists).loadingPlaceholderis shown while the worker is still searching (x-brandicons-state: loading).placeholderis 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.
- Docs and live demo: /docs
- npm:
react-brandicons - Source: github.com/neocoder/react-brandicons
If you don’t have an API key yet, grab one — the Community plan is free forever and includes 500K requests per month.