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

# Build a customer logo wall with wordmarks

> Show customer logos people recognize. A trusted-by wall of wide wordmark lockups built from a domain list, with no logo files to collect or update.

export const LogoWallDemo = () => {
  const BRANDMARKS = [{
    name: "Stripe",
    slug: "stripe",
    h: 26
  }, {
    name: "Twilio",
    slug: "twilio",
    h: 26
  }, {
    name: "DoorDash",
    slug: "doordash",
    h: 20
  }, {
    name: "Vercel",
    slug: "vercel",
    h: 22
  }, {
    name: "Discord",
    slug: "discord",
    h: 24
  }, {
    name: "Airbnb",
    slug: "airbnb",
    h: 25
  }, {
    name: "Spotify",
    slug: "spotify",
    h: 26
  }, {
    name: "Datadog",
    slug: "datadog",
    h: 19
  }];
  const [loaded, setLoaded] = useState({});
  const [assetBase] = useState(() => typeof window !== "undefined" && window.location.pathname.startsWith("/docs") ? "/docs" : "");
  const markLoaded = key => setLoaded(m => m[key] ? m : {
    ...m,
    [key]: true
  });
  const src = (slug, theme) => `${assetBase}/images/use-cases/logo-wall/${slug}-${theme}.webp`;
  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">
      <ul className="mx-auto grid max-w-lg grid-cols-2 items-center gap-x-10 gap-y-9 sm:grid-cols-4">
        {BRANDMARKS.map(brand => <li className="flex h-8 items-center justify-center" key={brand.slug}>
            {["light", "dark"].map(theme => {
    const key = `${brand.slug}-${theme}`;
    return <span className={`relative flex items-center justify-center ${theme === "light" ? "dark:hidden" : "hidden dark:flex"}`} key={key}>
                  {!loaded[key] && <span aria-hidden="true" className="h-5 w-24 animate-pulse rounded bg-zinc-200 motion-reduce:animate-none dark:bg-zinc-700" />}
                  <img alt={`${brand.name} logo`} className={`w-auto max-w-full object-contain transition-opacity duration-300 motion-reduce:transition-none ${loaded[key] ? "opacity-100" : "absolute opacity-0"}`} loading="lazy" onLoad={() => markLoaded(key)} ref={el => {
      if (el?.complete && el.naturalWidth > 0) {
        markLoaded(key);
      }
    }} src={src(brand.slug, theme)} style={{
      height: brand.h
    }} />
                </span>;
  })}
          </li>)}
      </ul>
    </div>;
};

A trusted-by wall needs the wide wordmark lockup of each logo, not the square icon. A row of square icons reads like an app grid. The wide lockups are the ones visitors recognize.

A visitor scrolling your landing page sees an even row of familiar wordmarks in the right theme. You feed the wall customer domains, and the [Brand API](/docs/brand/introduction) returns each wide lockup in its `brandmark` field, so you never collect or host a logo file.

## Demo

Switch the docs theme and the wall swaps every logo to its `theme=dark` variant:

<LogoWallDemo />

Each logo is a Brand API `brandmark`, fetched once and committed because the API takes a secret key and its asset URLs can rotate.

## Build it with AI

<Prompt description="Want this customer wall in your app? This prompt gives your AI coding tool everything it needs to build it in your framework." actions={["copy", "cursor"]}>
  Build a customer logo wall (a trusted-by section) using Logo.dev wordmark lockups.

  * Fetch each customer's wide logo server-side from the Logo.dev Brand API: GET [https://api.logo.dev/brand/:domain](https://api.logo.dev/brand/:domain) with header Authorization: Bearer LOGO\_DEV\_SECRET\_KEY. Use the brandmark field of the response; it is a ready-to-render image URL. Skip brands where brandmark is null.
  * Append size=160\&retina=true\&format=webp to each brandmark URL, store the URLs, and render the wall client-side as plain images.
  * Render a responsive grid (2 columns on mobile, 4 on desktop). Give each logo its own optical height in px (default 24), tuned by eye, because all-caps marks look louder than lowercase wordmarks at the same height.
  * Show a skeleton in each slot until its image loads, then fade the image in. Respect prefers-reduced-motion.
  * Brandmark URLs do not fall back to a monogram, so on image error fall back to the square icon URL, which does: [https://img.logo.dev/:domain?token=LOGO\_DEV\_PUBLISHABLE\_KEY\&size=24\&retina=true\&format=webp](https://img.logo.dev/:domain?token=LOGO_DEV_PUBLISHABLE_KEY\&size=24\&retina=true\&format=webp)
  * Append theme=dark to brandmark URLs rendered on dark backgrounds so wordmarks stay legible.
  * The secret key stays on the server; the publishable key is safe in client code.
  * Reference implementation: [https://www.logo.dev/docs/use-cases/customer-logo-wall](https://www.logo.dev/docs/use-cases/customer-logo-wall)
</Prompt>

## Code

This example is in React, but you can get the same result in any frontend framework: the wall is stored brandmark URLs rendered as plain images. Fetch each brandmark once with `get-brandmarks.ts`, store the URL, and render the wall as the Usage tab shows.

<CodeGroup>
  ```ts get-brandmarks.ts theme={null}
  type Brand = { name: string; brandmark: string | null };

  export async function getBrandmark(domain: string) {
    const res = await fetch(`https://api.logo.dev/brand/${domain}`, {
      headers: { Authorization: `Bearer ${process.env.LOGO_DEV_SECRET_KEY}` },
    });
    if (!res.ok) {
      return null;
    }
    const brand = (await res.json()) as Brand;
    // brandmark is null when a brand has no wordmark asset.
    return brand.brandmark;
  }
  ```

  ```tsx LogoWall.tsx expandable theme={null}
  import { useState } from "react";

  type WallLogo = {
    name: string;
    domain: string;
    brandmark: string;
    /** Optical height in px, tuned by eye. Defaults to 24. */
    height?: number;
  };

  function WallLogoItem({ logo, token }: { logo: WallLogo; token: string }) {
    const [state, setState] = useState<"loading" | "loaded" | "failed">(
      "loading"
    );

    // Brandmark URLs do not fall back to a monogram the way square logo URLs
    // do, so a failed asset degrades to the square icon, which does. The chain
    // is brandmark, then icon, then monogram: the wall can never break.
    if (state === "failed") {
      return (
        <img
          alt={`${logo.name} logo`}
          className="h-6 w-6 rounded-md object-contain"
          height={24}
          src={`https://img.logo.dev/${logo.domain}?token=${token}&size=24&retina=true&format=webp`}
          width={24}
        />
      );
    }

    return (
      <>
        {state === "loading" && (
          <span
            aria-hidden="true"
            className="h-5 w-24 animate-pulse rounded bg-zinc-200 motion-reduce:animate-none"
          />
        )}
        <img
          alt={`${logo.name} logo`}
          className={`w-auto max-w-full object-contain transition-opacity duration-300 motion-reduce:transition-none ${
            state === "loaded" ? "opacity-100" : "absolute opacity-0"
          }`}
          loading="lazy"
          onError={() => setState("failed")}
          onLoad={() => setState("loaded")}
          ref={(el) => {
            // Cached images can finish before React attaches onLoad, so check
            // on mount as well.
            if (el?.complete && el.naturalWidth > 0) {
              setState("loaded");
            }
          }}
          src={`${logo.brandmark}&size=160&retina=true&format=webp`}
          style={{ height: logo.height ?? 24 }}
        />
      </>
    );
  }

  export function LogoWall({
    logos,
    token,
  }: {
    logos: WallLogo[];
    token: string;
  }) {
    return (
      <ul className="grid grid-cols-2 items-center gap-x-10 gap-y-9 sm:grid-cols-4">
        {logos.map((logo) => (
          <li
            className="relative flex h-8 items-center justify-center"
            key={logo.brandmark}
          >
            <WallLogoItem logo={logo} token={token} />
          </li>
        ))}
      </ul>
    );
  }
  ```

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

  // brandmark URLs come from get-brandmarks.ts (truncated here).
  const logos = [
    { name: "Stripe", domain: "stripe.com", height: 26, brandmark: "https://img.logo.dev/stripe.com/bb98…?token=LOGO_DEV_PUBLISHABLE_KEY" },
    { name: "DoorDash", domain: "doordash.com", height: 20, brandmark: "https://img.logo.dev/doordash.com/2e9b…?token=LOGO_DEV_PUBLISHABLE_KEY" },
  ];

  export function TrustedBy() {
    return <LogoWall logos={logos} token="LOGO_DEV_PUBLISHABLE_KEY" />;
  }
  ```
</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. Your secret key powers the
  `get-brandmarks.ts` fetch and stays on the server.
</Note>

## How it works

* **The brandmark is the wide lockup.** `GET /brand/:domain` from the [Brand API](/docs/brand/introduction) returns the rectangular version that customer walls are made of. For most brands that is the icon and wordmark together; for brands whose official logo is pure type, it is the wordmark.
* **A failed asset falls back to the square icon.** Brandmark URLs do not get the monogram fallback that square logo URLs have, which is why the component falls back to the icon URL. The chain runs brandmark, then icon, then monogram, so the wall never shows a broken image.
* **Theme variants keep wordmarks legible.** Append `theme=dark` for dark backgrounds, and use the usual `size`, `retina`, and `format` [image parameters](/docs/logo-images/introduction#parameters) on any brandmark URL.
* **Each slot handles its own polish.** A skeleton holds the space, the logo fades in, and a per-logo optical height keeps the rows reading evenly.

## Make it your own

* **Handle missing brandmarks at fetch time.** Some brands return `brandmark: null` from the [Brand API](/docs/brand/introduction). Skip them or store the square icon URL instead.
* **Mute the wall.** Append `&greyscale=true` to every URL for the classic monochrome treatment. See all [image parameters](/docs/logo-images/introduction#parameters).
* **Animate it.** Use the [animated logo wall](/docs/use-cases/animated-logo-wall) for a wall that swaps logos over time.

## Next steps

<CardGroup cols={2}>
  <Card title="Brand API" icon="swatchbook" href="/docs/brand/introduction">
    Get the full brand profile: logo, brandmark, banners, and colors.
  </Card>

  <Card title="Animated logo wall" icon="grip" href="/docs/use-cases/animated-logo-wall">
    Make the wall swap logos over time.
  </Card>
</CardGroup>
