> ## Documentation Index
> Fetch the complete documentation index at: https://www.logo.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Get a company logo from a domain

> Show the right company logo every time. One image URL handles dark mode, retina screens, and companies with no logo at all.

export const CompanyLogoDemo = () => {
  const TOKEN = "live_6a1a28fd-6420-4492-aeb0-b297461d9de2";
  const PRESETS = ["stripe.com", "github.com", "made-up-startup.io"];
  const [input, setInput] = useState("");
  const [domain, setDomain] = useState("github.com");
  const [failedDomain, setFailedDomain] = useState(null);
  const [theme, setTheme] = useState("light");
  const [greyscale, setGreyscale] = useState(false);
  const failed = failedDomain === domain;
  const normalize = value => value.trim().toLowerCase().replace(/^https?:\/\//, "").replace(/^www\./, "").split("/")[0];
  const apply = value => {
    const next = normalize(value);
    if (next.includes(".")) {
      setDomain(next);
    }
  };
  const src = `https://img.logo.dev/${domain}?token=${TOKEN}&size=56&retina=true&format=webp&theme=${theme}${greyscale ? "&greyscale=true" : ""}&fallback=404`;
  const toggleClass = isActive => `relative rounded-full border px-3.5 py-2 text-xs font-medium transition-[background-color,scale] duration-150 before:absolute before:-inset-1 before:content-[''] focus:outline-none focus-visible:ring-2 focus-visible:ring-zinc-950/20 motion-safe:active:scale-[0.96] dark:focus-visible:ring-white/20 ${isActive ? "border-zinc-950/20 bg-zinc-100 text-zinc-950 dark:border-white/25 dark:bg-zinc-800 dark:text-white" : "border-zinc-950/10 bg-white text-zinc-600 hover:bg-zinc-50 dark:border-white/15 dark:bg-zinc-900 dark:text-zinc-300 dark:hover:bg-zinc-800"}`;
  return <div className="not-prose my-8 rounded-2xl border border-zinc-950/10 bg-gradient-to-br from-zinc-50 to-white p-6 shadow-sm sm:p-8 dark:border-white/10 dark:from-zinc-900 dark:to-zinc-950">
      <div className="mx-auto max-w-md space-y-5">
        <div className={`flex flex-col items-center gap-2 rounded-xl border py-8 ${theme === "dark" ? "border-white/10 bg-zinc-950" : "border-zinc-950/10 bg-white"}`}>
          {failed ? <div aria-label={`${domain} initials avatar`} className="flex h-14 w-14 items-center justify-center rounded-xl bg-zinc-400/30 text-xl font-semibold text-zinc-500" role="img">
              {domain.charAt(0).toUpperCase()}
            </div> : <img alt={`${domain} logo on a ${theme} background`} className="h-14 w-14 object-contain" height="56" key={`${domain}-${theme}-${greyscale}`} onError={() => setFailedDomain(domain)} src={src} width="56" />}
          <span className={`font-mono text-[11px] ${theme === "dark" ? "text-zinc-500" : "text-zinc-400"}`}>
            theme={theme}
            {greyscale ? "&greyscale=true" : ""}
          </span>
        </div>
        <div className="flex flex-wrap items-center gap-2">
          <div aria-label="Background theme" className="flex gap-1" role="group">
            <button aria-pressed={theme === "light"} className={toggleClass(theme === "light")} onClick={() => setTheme("light")} type="button">
              Light surface
            </button>
            <button aria-pressed={theme === "dark"} className={toggleClass(theme === "dark")} onClick={() => setTheme("dark")} type="button">
              Dark surface
            </button>
          </div>
          <button aria-pressed={greyscale} className={toggleClass(greyscale)} onClick={() => setGreyscale(g => !g)} type="button">
            Greyscale
          </button>
        </div>
        <div>
          <label className="sr-only" htmlFor="company-logo-input">
            Enter a domain
          </label>
          <input className="w-full rounded-xl border border-zinc-950/10 bg-white px-4 py-3 text-base text-zinc-950 placeholder:text-zinc-400 focus:outline-none focus:ring-2 focus:ring-zinc-950/20 dark:border-white/20 dark:bg-zinc-900 dark:text-white dark:focus:ring-white/20" id="company-logo-input" onChange={e => {
    setInput(e.target.value);
    apply(e.target.value);
  }} placeholder="Try any domain…" type="text" value={input} />
          <div className="mt-3 flex flex-wrap gap-2">
            {PRESETS.map(preset => <button className={toggleClass(domain === preset)} key={preset} onClick={() => {
    setInput(preset);
    apply(preset);
  }} type="button">
                {preset}
              </button>)}
          </div>
        </div>
      </div>
    </div>;
};

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](/docs/logo-images/get) image URL:

```html theme={null}
<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:

<CompanyLogoDemo />

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

<Prompt description="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." actions={["copy", "cursor"]}>
  Build a company logo component using Logo.dev.

  * Render a company's logo from its domain. Logos are plain image URLs: [https://img.logo.dev/:domain?token=LOGO\_DEV\_PUBLISHABLE\_KEY\&size=40\&retina=true\&format=webp](https://img.logo.dev/:domain?token=LOGO_DEV_PUBLISHABLE_KEY\&size=40\&retina=true\&format=webp)
  * Support light and dark surfaces: add theme=dark to the URL for dark backgrounds, and use a picture element with a prefers-color-scheme source so the browser switches automatically.
  * Add fallback=404 to the URL and catch the image error: when a company has no logo, render an initials avatar (or a caller-provided fallback) at the same fixed size.
  * Reset the error state when the domain changes so a new domain retries the lookup.
  * Set fixed width and height so nothing shifts, with lazy loading and async decoding.
  * Use a Logo.dev publishable key (safe in client code), never a secret key.
  * Reference implementation: [https://www.logo.dev/docs/use-cases/logo-from-domain](https://www.logo.dev/docs/use-cases/logo-from-domain)
</Prompt>

## 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.

<CodeGroup>
  ```tsx CompanyLogo.tsx expandable theme={null}
  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>
    );
  }
  ```

  ```tsx Usage theme={null}
  import { CompanyLogo } from "./CompanyLogo";

  export function CompanyList() {
    return (
      <div>
        <CompanyLogo
          domain="github.com"
          name="GitHub"
          size={40}
          token="LOGO_DEV_PUBLISHABLE_KEY"
        />
        {/* Fixed dark surface (a sidebar, a footer): */}
        <CompanyLogo
          domain="github.com"
          theme="dark"
          token="LOGO_DEV_PUBLISHABLE_KEY"
        />
        {/* Custom fallback instead of the initials avatar: */}
        <CompanyLogo
          domain="made-up-startup.io"
          fallback={<span className="inline-block h-10 w-10 rounded-lg bg-zinc-200" />}
          token="LOGO_DEV_PUBLISHABLE_KEY"
        />
      </div>
    );
  }
  ```
</CodeGroup>

<Note>
  Your [publishable key](/docs/platform/api-keys) is built for client-side code, so
  you can ship it in the browser as-is.
</Note>

## 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](/docs/logo-images/introduction#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](/docs/logo-images/introduction#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](/docs/logo-images/introduction#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](/docs/logo-images/introduction#fallback-images).
* **Start from names instead.** Use the [logo from a name](/docs/use-cases/logo-from-name) pattern when your data has company names without domains.

## Next steps

<CardGroup cols={2}>
  <Card title="Logo API" icon="image" href="/docs/logo-images/get">
    Look up every parameter the domain endpoint accepts: size, format, theme, greyscale, and fallbacks.
  </Card>

  <Card title="Logo from a name" icon="font" href="/docs/use-cases/logo-from-name">
    Render logos from company names when your lists have no domains.
  </Card>
</CardGroup>
