# Product Logo

A product logo image with automatic fallbacks: falls back to the site favicon when the logo fails, then to uppercase initials when no image loads.

- Category: Data Display
- License: MIT
- Page: https://www.saasuji.com/components/product-logo
- Install: npx shadcn@latest add https://www.saasuji.com/r/product-logo.json

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| src | `string | null | undefined` | — | Logo image URL; required. Null, empty, or failed sources fall back to the favicon or initials. |
| name | `string` | — | Product name; required. Its first two characters become the initials fallback. |
| websiteUrl | `string | null` | — | Site URL used to derive a Google favicon fallback when the logo is missing or fails. |
| size | `number` | `48` | Width and height in pixels for both the image and the initial tile. |
| className | `string` | — | Additional classes merged onto the image or fallback tile. |
| style | `React.CSSProperties` | — | Inline styles applied alongside the size dimensions. |
| priority | `boolean` | `false` | Loads the logo eagerly and sets fetchPriority to high. |

## Source

```tsx
"use client";

import { useState } from "react";
import Image from "next/image";
import { cn } from "@/lib/utils";

interface ProductLogoProps {
  src: string | null | undefined;
  websiteUrl?: string | null;
  name: string;
  size?: number;
  className?: string;
  style?: React.CSSProperties;
  priority?: boolean;
}

function getFaviconUrl(websiteUrl: string): string {
  try {
    const url = new URL(websiteUrl);
    return `https://www.google.com/s2/favicons?domain=${url.hostname}&sz=128`;
  } catch {
    return "";
  }
}

export function ProductLogo({
  src,
  websiteUrl,
  name,
  size = 48,
  className,
  style,
  priority = false,
}: ProductLogoProps) {
  const key = `${src || ""}|${websiteUrl || ""}`;
  const [prevKey, setPrevKey] = useState(key);
  const [srcFailed, setSrcFailed] = useState(false);
  const [faviconFailed, setFaviconFailed] = useState(false);

  if (key !== prevKey) {
    setPrevKey(key);
    setSrcFailed(false);
    setFaviconFailed(false);
  }

  const faviconUrl = websiteUrl && !faviconFailed ? getFaviconUrl(websiteUrl) : null;
  const displaySrc = (!srcFailed && src) ? src : faviconUrl;

  if (!displaySrc || (srcFailed && faviconFailed) || (!src && !faviconUrl)) {
    return (
      <span
        className={cn(
          "rounded-lg bg-secondary border border-border flex items-center justify-center font-bold text-foreground shrink-0 select-none",
          size > 40 ? "text-sm" : "text-[11px]",
          className
        )}
        style={{ width: size, height: size, ...style }}
      >
        {name.substring(0, 2).toUpperCase()}
      </span>
    );
  }

  return (
    <Image
      src={displaySrc}
      alt={`${name} logo`}
      onError={() => {
        if (!srcFailed && src) {
          setSrcFailed(true);
        } else {
          setFaviconFailed(true);
        }
      }}
      className={cn("rounded-lg object-cover shrink-0", className)}
      unoptimized
      width={size}
      height={size}
      sizes={`${size}px`}
      priority={priority}
      fetchPriority={priority ? "high" : undefined}
      style={{ width: size, height: size, ...style }}
    />
  );
}

```
