A balanced grid of company logos displayed harmoniously on a landing page

React Logo Soup: Making Logos Play Nice Together

The Orange Cat
The Orange Cat

Every marketing site has one: the "Trusted By" section. A row of brand logos that's supposed to radiate credibility. In practice, it radiates chaos. One logo is razor-thin and wide, another is a dense square, a third ships with enough baked-in padding to park a truck. You spend an hour nudging CSS values, and then someone from marketing Slacks you a new logo and the whole strip falls apart. react-logo-soup fixes this with a mathematical normalization formula that makes any set of logos look like they were designed to sit together.

The Curse of the Logo Strip

If you've tried to line up a dozen brand logos, you already know the pain. Setting them all to the same width makes square logos tower over wide ones. Setting them to the same height lets wide logos dominate the row. Manual tweaking works until someone adds or removes a logo and the whole thing needs rebalancing.

The root issue is that logos differ across multiple dimensions at once: aspect ratio, visual density, baked-in whitespace, and optical center of mass. CSS alone can handle one of these at best. React Logo Soup handles all four automatically, using client-side canvas analysis and a proportional normalization formula.

What It Brings to the Table

  • Aspect ratio normalization using a power-function formula that dampens extremes
  • Density-aware scaling that shrinks heavy logos and grows light ones so visual weight feels even
  • Content boundary detection that ignores baked-in padding by analyzing actual pixel data
  • Visual center alignment so logos look optically centered, not just geometrically centered
  • Custom render support for plugging in Next.js Image, lazy loading, or any image component
  • A headless hook for building completely custom layouts while still getting normalized dimensions

Setting Up the Kitchen

Install with your package manager of choice:

npm install react-logo-soup
yarn add react-logo-soup

react-logo-soup has zero runtime dependencies. It needs React 18 or 19 and TypeScript 5 as peer dependencies.

Stirring Up Your First Bowl

The Quick Drop-In

The simplest usage is the LogoSoup component with an array of logo objects:

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

function TrustedBy() {
  const logos = [
    { src: "/logos/acme.svg", alt: "Acme Corp" },
    { src: "/logos/globex.svg", alt: "Globex Corporation" },
    { src: "/logos/initech.svg", alt: "Initech" },
    { src: "/logos/umbrella.svg", alt: "Umbrella Corp" },
    { src: "/logos/stark.svg", alt: "Stark Industries" },
  ];

  return (
    <section>
      <h2>Trusted by industry leaders</h2>
      <LogoSoup logos={logos} />
    </section>
  );
}

That's it. The component loads each image, analyzes its pixel data on a canvas, computes normalized dimensions, and renders them in a balanced row. No manual sizing needed.

Tuning the Flavor

The defaults work well for most cases, but you can fine-tune the formula with a few props:

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

function SponsorStrip() {
  return (
    <LogoSoup
      logos={sponsors}
      baseSize={64}
      gap={24}
      scaleFactor={0.525}
      densityFactor={0.7}
      alignBy="visual-center"
    />
  );
}

The baseSize sets the target dimension for a perfectly square logo. The scaleFactor controls how aspect ratios are balanced: 0 gives uniform width, 1 gives uniform height, and 0.5 splits the difference. The densityFactor controls how aggressively the library compensates for visual weight differences between dense and light logos.

Stripping Away Hidden Padding

Some logos ship with generous whitespace baked into the image file. The cropToContent prop tells the library to detect actual content boundaries and crop accordingly:

<LogoSoup
  logos={logos}
  cropToContent={true}
  baseSize={48}
/>

When enabled, the component analyzes pixel data to find where the logo content actually lives, ignores the surrounding empty space, and returns base64-encoded cropped images. This means you can accept logos from anyone in any format and they'll still line up correctly.

Cooking Something Custom

The Headless Hook

When the built-in grid layout doesn't fit your design, the useLogoSoup hook gives you normalized dimensions without any rendering opinions:

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

function CustomLogoMarquee() {
  const { isLoading, normalizedLogos } = useLogoSoup({
    logos: [
      { src: "/logos/alpha.svg", alt: "Alpha" },
      { src: "/logos/beta.svg", alt: "Beta" },
      { src: "/logos/gamma.svg", alt: "Gamma" },
    ],
    baseSize: 56,
    densityFactor: 0.6,
  });

  if (isLoading) {
    return <div className="logo-skeleton" />;
  }

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

The hook returns an isLoading flag since canvas analysis is asynchronous, plus an array of logo objects enriched with normalizedWidth and normalizedHeight. The getVisualCenterTransform helper computes the CSS transform needed to optically center each logo.

Plugging In Framework-Specific Images

If you're using Next.js or any other framework with a custom image component, the renderImage prop lets you swap out the default <img> tag:

import Image from "next/image";
import { LogoSoup } from "react-logo-soup";

function OptimizedLogoStrip() {
  return (
    <LogoSoup
      logos={logos}
      renderImage={(props) => (
        <Image
          {...props}
          loading="lazy"
          decoding="async"
          quality={90}
        />
      )}
    />
  );
}

This gives you full control over how each logo image is rendered while still benefiting from the normalization engine.

Alignment Strategies

The alignBy prop supports four alignment modes, each useful for different layouts:

// Geometric center — simple bounding box alignment
<LogoSoup logos={logos} alignBy="bounds" />

// Full visual center — accounts for weight in both axes
<LogoSoup logos={logos} alignBy="visual-center" />

// Horizontal visual center only — good for vertical stacks
<LogoSoup logos={logos} alignBy="visual-center-x" />

// Vertical visual center only — the default, ideal for horizontal rows
<LogoSoup logos={logos} alignBy="visual-center-y" />

The difference between bounds and visual-center is subtle but real. A logo with a heavy mark on one side and light text on the other will appear off-center with geometric alignment. Visual center alignment shifts it so the perceived center of mass lines up.

The Math Behind the Magic

The normalization formula is surprisingly simple. For each logo, the computed display width is:

(imageWidth / imageHeight) ^ scaleFactor * baseSize

When the scale factor is 0.5, a logo that's four times as wide as it is tall gets displayed at twice the base size width — not four times. The square root naturally compresses extreme aspect ratios toward a middle ground. This single formula handles everything from nearly-square icons to ultra-wide wordmarks without any special casing.

On top of that, the density analysis measures how much of each logo's bounding box is actually filled with pixels. A bold, solid logomark gets scaled down slightly; a thin, airy wordmark gets scaled up. The result is that every logo in the row carries roughly the same visual weight.

When to Reach for the Soup Ladle

React Logo Soup shines in any scenario where non-technical teams manage logo assets: "Trusted By" strips, sponsor sections, integration showcases, partner grids, and press mention bars. The key selling point is that content editors can upload whatever logo file they have, in whatever dimensions, with whatever padding, and the output looks intentionally designed.

The library processes everything client-side on canvas with zero runtime dependencies beyond React. It's written entirely in TypeScript, so you get full type safety and autocompletion out of the box. And since the normalization engine is framework-agnostic JavaScript under the hood, ports to Vue, Svelte, or vanilla JS are straightforward if your needs expand beyond React.

If your logo strip has ever made you wince, give react-logo-soup a try. Let math handle the pixel-pushing so you can get back to building the rest of the page.