Skip to content
goodtabs UI
BrowseBrowse components

Logo cloud

A static grid of customer marks on hairlines that rise from muted to full ink on hover.

npx shadcn@latest add https://ui.goodtabs.co/r/logo-cloud.json

With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/logo-cloud

Trusted by small teams

  • Northwind
  • Halcyon
  • Quarry
  • Tidemark
  • Lumen & Co
  • Parallel
  • Okra
  • Ferrous

Eight made-up wordmarks on a hairline grid.

Five wide

  • Northwind
  • Halcyon
  • Quarry
  • Tidemark
  • Lumen & Co
  • Parallel
  • Okra
  • Ferrous
  • Mosaic Labs
  • Vesper

Ten marks across five columns.

Usage

import { LogoCloud } from "@/components/logo-cloud"

<LogoCloud title="Trusted by small teams" logos={[{ name: "Northwind", mark: <span>Northwind</span> }]} />

Props

PropTypeDefaultDescription
titlestringSmall mono line above the grid.
logos{ name: string; mark: ReactNode }[]Each mark is an SVG or styled text. The name is read by screen readers.
columns3 | 4 | 54Columns from sm up; two on a phone.

Source

logo-cloud/logo-cloud.tsx
import * as React from "react";
import { cn } from "@/lib/utils";

export type Logo = {
  /** Company name, read by screen readers. */
  name: string;
  /** The mark: an inline SVG or styled text. Decorative, so it is hidden from assistive tech. */
  mark: React.ReactNode;
};

type LogoCloudProps = Omit<React.ComponentProps<"section">, "title"> & {
  /** Small line above the grid. */
  title?: string;
  logos: Logo[];
  /** Columns from the sm breakpoint up. Two on a phone. */
  columns?: 3 | 4 | 5;
};

const cols = {
  3: "sm:grid-cols-3",
  4: "sm:grid-cols-4",
  5: "sm:grid-cols-5",
};

/**
 * A static grid of customer marks separated by hairlines. Marks are muted and
 * rise to full ink on hover. Server component.
 */
export function LogoCloud({ title, logos, columns = 4, className, ...props }: LogoCloudProps) {
  return (
    <section data-slot="logo-cloud" className={cn("w-full px-6 py-10 md:px-8", className)} {...props}>
      <div className="mx-auto flex max-w-6xl flex-col gap-6">
        {title && (
          <p className="m-0 text-center font-mono text-xs tracking-wide text-muted-foreground uppercase">{title}</p>
        )}
        <ul
          className={cn(
            "m-0 grid list-none grid-cols-2 gap-px overflow-hidden rounded-lg border bg-border p-0",
            cols[columns],
          )}
        >
          {logos.map((logo) => (
            <li
              key={logo.name}
              className="grid h-20 place-items-center bg-background px-4 text-muted-foreground transition-colors duration-150 ease-out hover:text-foreground"
            >
              <span className="sr-only">{logo.name}</span>
              <span aria-hidden className="flex items-center gap-2">
                {logo.mark}
              </span>
            </li>
          ))}
        </ul>
      </div>
    </section>
  );
}

For AI agents

Hand your agent this page, the markdown at /components/logo-cloud/prompt.md, or the Copy AI prompt button above.

When to use

  • Social proof under a hero
  • When you have six or more recognisable customers

When not to use

  • With one or two customers, where a quote is stronger
  • With a scrolling marquee; this one is static

Agent notes

  • Server component; marks are decorative and hidden from assistive tech, name is in sr-only
  • Pass currentColor marks so they follow the muted and hover tones
  • Use only logos you have permission to show; the demos use invented names