Skip to content

sanity-labs/react-logo-soup

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

62 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🍜 React Logo Soup

A tiny React library that makes logos look good together.

The Problem

Real-world logos are messy. Some have padding, some don't. Some are dense and blocky, others are thin and airy. Put them in a row and they look chaotic.

Logos without normalization β€” different sizes, weights, and aspect ratios create visual chaos

React Logo Soup fixes this automatically.

After normalization β€” the same logos appear balanced and harmonious

Read the full deep-dive: The Logo Soup Problem (and how to solve it)

Getting Started

npm install react-logo-soup
import { LogoSoup } from "react-logo-soup";

function LogoStrip() {
  return (
    <LogoSoup
      logos={[
        { src: "/logos/acme.svg", alt: "Acme Corp" },
        { src: "/logos/globex.svg", alt: "Globex" },
        { src: "/logos/initech.svg", alt: "Initech" },
      ]}
    />
  );
}

That's it! React Logo Soup will analyze each logo and normalize them to look visually balanced.

Logos can be plain URL strings or objects with alt text. Use objects to provide accessible alt text for each logo. The alt text is passed directly to the underlying <img> tag.

Options

gap

Space between logos. Default is 16.

<LogoSoup logos={logos} gap={24} />

baseSize

How big the logos should be, in pixels. Default is 48.

<LogoSoup logos={logos} baseSize={64} />

densityAware and densityFactor

React Logo Soup measures the "visual weight" of each logo. Dense, solid logos get scaled down. Light, thin logos get scaled up. This is on by default.

  • densityAware={false} β€” Turn it off
  • densityFactor β€” How strong the effect is (0 = off, 0.5 = default, 1 = strong)
// Stronger density compensation
<LogoSoup logos={logos} densityFactor={0.8} />

// Turn it off
<LogoSoup logos={logos} densityAware={false} />

scaleFactor

How to handle logos with different shapes (wide vs tall). Default is 0.5.

Imagine you have two logos:

  • Logo A: wide (200Γ—100)
  • Logo B: tall (100Γ—200)

scaleFactor = 0 β†’ Same width for all logos

  • Logo A: 48Γ—24 (short)
  • Logo B: 48Γ—96 (very tall)

scaleFactor = 1 β†’ Same height for all logos

  • Logo A: 96Γ—48 (very wide)
  • Logo B: 24Γ—48 (narrow)

scaleFactor = 0.5 β†’ Balanced (default)

  • Neither gets too wide nor too tall
  • Looks most natural
<LogoSoup logos={logos} scaleFactor={0.5} />

alignBy

How to align logos. Default is "visual-center-y".

  • "bounds" β€” Align by geometric center (bounding box)
  • "visual-center" β€” Align by visual weight center (accounts for asymmetric logos)
  • "visual-center-x" β€” Align by visual weight center horizontally only
  • "visual-center-y" β€” Align by visual weight center vertically only
<LogoSoup logos={logos} alignBy="visual-center" />

cropToContent

When enabled, logos are cropped to their content bounds and returned as base64 images. This removes any whitespace/padding baked into the original image files. Default is false.

<LogoSoup logos={logos} cropToContent />

Using the Hook

For custom layouts, use the useLogoSoup hook directly:

import { useLogoSoup } from "react-logo-soup";

function CustomGrid() {
  const { isLoading, normalizedLogos } = useLogoSoup({
    logos: [
      { src: "/logo1.svg", alt: "Logo 1" },
      { src: "/logo2.svg", alt: "Logo 2" },
    ],
  });

  if (isLoading) return <div>Loading...</div>;

  return (
    <div className="grid">
      {normalizedLogos.map((logo) => (
        <img
          key={logo.src}
          src={logo.src}
          alt={logo.alt}
          width={logo.normalizedWidth}
          height={logo.normalizedHeight}
        />
      ))}
    </div>
  );
}

getVisualCenterTransform

When using the hook, you can apply visual center alignment with the getVisualCenterTransform helper:

import { useLogoSoup, getVisualCenterTransform } from "react-logo-soup";

function CustomGrid() {
  const { normalizedLogos } = useLogoSoup({ logos });

  return (
    <div className="grid">
      {normalizedLogos.map((logo) => (
        <img
          key={logo.src}
          src={logo.src}
          width={logo.normalizedWidth}
          height={logo.normalizedHeight}
          style={{ transform: getVisualCenterTransform(logo, "visual-center") }}
        />
      ))}
    </div>
  );
}

Custom Image Component

Use renderImage to render logos with Next.js Image, add extra attributes, or fully control the <img> output. The callback receives all standard <img> attributes (src, alt, width, height, style):

import Image from "next/image";

<LogoSoup
  logos={logos}
  renderImage={(props) => (
    <Image
      src={props.src}
      alt={props.alt}
      width={props.width}
      height={props.height}
    />
  )}
/>;

You can also use it to add attributes like loading or className:

<LogoSoup
  logos={logos}
  renderImage={(props) => <img {...props} loading="lazy" decoding="async" />}
/>;

How It Works

  1. Content Detection β€” Analyzes each logo to find its true boundaries, ignoring whitespace and padding
  2. Aspect Ratio Normalization β€” Scales logos based on their shape using the scaleFactor
  3. Density Compensation β€” Measures pixel density and adjusts size so dense logos don't overpower light ones

All processing happens client-side using canvas. No AI, fully deterministic.

Porting to Other Frameworks

The normalization core is plain JavaScript β€” React is just the wrapper. If you'd like to port Logo Soup to Vue, Svelte, Angular, or any other framework, go for it! We'd appreciate a link back to this repo, and let us know so we can link to your port from here.

Community Ports

  • Logo Soup β€” Vanilla HTML/JS implementation, no framework required

Development

bun install
bun test
bun run storybook

License

MIT