Skip to main content
Any app that lists companies needs their logos: CRM rows, vendor dashboards, customer directories. One logo is one URL; showing hundreds reliably means surviving dark surfaces, retina screens, and companies with no logo at all. A user opens the list and every row carries a crisp mark, matched to the surface, with an initials avatar where no logo exists. Each one starts as a single Logo API image URL:
<img src="https://img.logo.dev/github.com?token=LOGO_DEV_PUBLISHABLE_KEY" alt="GitHub logo" />

Demo

Switch to the dark surface and watch GitHub’s black mark invert, then try a made-up domain to see the fallback: Every logo loads live from img.logo.dev as you toggle. The made-up domain shows the initials avatar instead of a broken image.

Build it with AI

Want the right logo for every company in your app? This prompt gives your AI coding tool everything it needs to build it in your framework.

Open in Cursor

Code

This example is in React, but you can get the same result in any frontend framework: the logo is one image URL, and dark mode rides on a standard <picture> element. Paste CompanyLogo.tsx, then render it as the Usage tab shows.
import { useState, type ReactNode } from "react";

type CompanyLogoProps = {
  domain: string;
  token: string;
  /** Rendered size in px. The API serves 2x pixels for retina screens. */
  size?: number;
  /** Display name for alt text and the default initials fallback. */
  name?: string;
  /** "auto" follows the user's color scheme. Force "light" or "dark" to match a fixed surface. */
  theme?: "auto" | "light" | "dark";
  greyscale?: boolean;
  /** Custom fallback UI. Defaults to an initials avatar. */
  fallback?: ReactNode;
  className?: string;
};

export function CompanyLogo({
  domain,
  token,
  size = 40,
  name,
  theme = "auto",
  greyscale = false,
  fallback,
  className = "",
}: CompanyLogoProps) {
  // Tracking *which* domain failed (not just a boolean) means the state
  // resets itself when the domain prop changes.
  const [failedDomain, setFailedDomain] = useState<string | null>(null);
  const label = name ?? domain;

  if (failedDomain === domain) {
    return (
      fallback ?? (
        <span
          aria-label={`${label} initials avatar`}
          className={`inline-flex items-center justify-center rounded-lg bg-zinc-200 font-semibold text-zinc-600 dark:bg-zinc-700 dark:text-zinc-200 ${className}`}
          role="img"
          style={{ width: size, height: size, fontSize: size * 0.45 }}
        >
          {label.charAt(0).toUpperCase()}
        </span>
      )
    );
  }

  const src = (t: "light" | "dark") =>
    `https://img.logo.dev/${domain}?token=${token}&size=${size}&retina=true&format=webp&theme=${t}${
      greyscale ? "&greyscale=true" : ""
    }&fallback=404`;

  const img = (
    <img
      alt={`${label} logo`}
      className={`object-contain ${className}`}
      decoding="async"
      height={size}
      loading="lazy"
      onError={() => setFailedDomain(domain)}
      src={src(theme === "dark" ? "dark" : "light")}
      width={size}
    />
  );

  if (theme !== "auto") {
    return img;
  }

  // theme="auto": the browser switches sources with the user's color scheme.
  return (
    <picture>
      <source media="(prefers-color-scheme: dark)" srcSet={src("dark")} />
      {img}
    </picture>
  );
}
Your publishable key is built for client-side code, so you can ship it in the browser as-is.

How it works

  • One image URL returns the logo. img.logo.dev/:domain serves the company’s mark, and parameters do the tuning: size for dimensions, retina=true for sharp rendering, format=webp for weight. See all image parameters.
  • fallback=404 puts misses in your hands. Unknown domains return a generated monogram by default; adding fallback=404 turns a miss into an image error the component catches with its own avatar. See fallback images.
  • theme=dark keeps dark marks legible. It inverts predominantly dark logos like GitHub’s for dark backgrounds, and most colored logos return the same file for both themes.
  • The browser does the switching. The component pairs a light URL with a dark URL, and the user’s color scheme picks between them, even when it changes live.

Make it your own

  • Mute a long list. Add &greyscale=true for uniform, low-contrast logo grids. See all image parameters.
  • Keep the monogram. Drop fallback=404 and the error path when Logo.dev’s generated lettermark is enough; the URL then always returns an image. See fallback images.
  • Start from names instead. Use the logo from a name pattern when your data has company names without domains.

Next steps

Logo API

Look up every parameter the domain endpoint accepts: size, format, theme, greyscale, and fallbacks.

Logo from a name

Render logos from company names when your lists have no domains.