Bloom Logo

Logo Clouds

A beautiful and highly customizable logo showcase component. Supports static/hoverable grids, infinite sliding marquees, and batch crossfade swap animations.

importimport { LogoClouds } from "@/components/ui/logoClouds/logoClouds";
$npx @bloomui-react/cli add logoClouds
logoClouds.tsx
"use client";

import { AnimatePresence, motion } from "framer-motion";
import * as React from "react";
import Marquee from "react-fast-marquee";
import { cn } from "@/lib/utils";

export interface Logo {
  id: string;
  name: string;
  logo: React.ReactNode;
}

const DEFAULT_LOGOS: Logo[] = [
  {
    id: "designo",
    name: "Designo",
    logo: (
      <svg
        className="h-8 w-auto fill-current"
        viewBox="0 0 134 32"
        xmlns="http://www.w3.org/2000/svg"
      >
        <path d="M18.6 16c0 5-3.6 9-8.6 9S1.4 21 1.4 16s3.6-9 8.6-9 8.6 4 8.6 9zm-2.8 0c0-3.3-2.3-5.8-5.8-5.8S4.2 12.7 4.2 16s2.3 5.8 5.8 5.8 5.8-2.5 5.8-5.8zM24 7.5h2.6v17H24zM32.5 16c0 5-3.6 9-8.6 9s-8.6-4-8.6-9 3.6-9 8.6-9 8.6 4 8.6 9zm-2.8 0c0-3.3-2.3-5.8-5.8-5.8S18 12.7 18 16s2.3 5.8 5.8 5.8 5.8-2.5 5.8-5.8zM42.2 16c0-5-3.6-9-8.6-9s-8.6 4-8.6 9 3.6 9 8.6 9c2.4 0 4.6-1 6-2.6v2.1h2.6V7.5h-2.6v2c-1.4-1.6-3.6-2.6-6-2.6-5 0-8.6 4-8.6 9s3.6 9 8.6 9 8.6-4 8.6-9zm-2.8 0c0 3.3-2.3 5.8-5.8 5.8S27.8 19.3 27.8 16s2.3-5.8 5.8-5.8 5.8 2.5 5.8 5.8zM56.4 16c0 5-3.6 9-8.6 9s-8.6-4-8.6-9 3.6-9 8.6-9 8.6 4 8.6 9zm-2.8 0c0-3.3-2.3-5.8-5.8-5.8S42 12.7 42 16s2.3 5.8 5.8 5.8 5.8-2.5 5.8-5.8z" />
        <circle cx="68" cy="16" r="8" className="text-sky-500 fill-current" />
        <text
          x="82"
          y="22"
          className="font-bold text-xl tracking-tight fill-current text-foreground"
        >
          DESIGNO
        </text>
      </svg>
    ),
  },
  {
    id: "aethelgard",
    name: "Aethelgard",
    logo: (
      <svg
        className="h-8 w-auto fill-current"
        viewBox="0 0 150 32"
        xmlns="http://www.w3.org/2000/svg"
      >
        <path
          d="M12 2L2 22h7l3-6h8l3 6h7L20 2H12zm2 6l3 6h-6l3-6z"
          className="text-purple-500 fill-current"
        />
        <text
          x="36"
          y="22"
          className="font-bold text-xl tracking-tight fill-current text-foreground"
        >
          AETHEL
        </text>
      </svg>
    ),
  },
  {
    id: "crypton",
    name: "Crypton",
    logo: (
      <svg
        className="h-8 w-auto fill-current"
        viewBox="0 0 140 32"
        xmlns="http://www.w3.org/2000/svg"
      >
        <path
          d="M4 6h10v6H4V6zm14 0h10v6H18V6zM4 16h24v10H4V16z"
          className="text-pink-500 fill-current"
        />
        <text
          x="36"
          y="23"
          className="font-bold text-xl tracking-tight fill-current text-foreground"
        >
          CRYPTON
        </text>
      </svg>
    ),
  },
  {
    id: "nexa",
    name: "Nexa",
    logo: (
      <svg
        className="h-8 w-auto fill-current"
        viewBox="0 0 120 32"
        xmlns="http://www.w3.org/2000/svg"
      >
        <path
          d="M16 2L2 16l14 14h8L10 16 24 2h-8z"
          className="text-emerald-500 fill-current"
        />
        <text
          x="32"
          y="22"
          className="font-bold text-xl tracking-tight fill-current text-foreground"
        >
          NEXA
        </text>
      </svg>
    ),
  },
  {
    id: "lumina",
    name: "Lumina",
    logo: (
      <svg
        className="h-8 w-auto fill-current"
        viewBox="0 0 130 32"
        xmlns="http://www.w3.org/2000/svg"
      >
        <circle
          cx="16"
          cy="16"
          r="10"
          className="text-amber-500 fill-current"
        />
        <path
          d="M16 6v20M6 16h20"
          stroke="currentColor"
          strokeWidth="2"
          strokeLinecap="round"
        />
        <text
          x="36"
          y="22"
          className="font-bold text-xl tracking-tight fill-current text-foreground"
        >
          LUMINA
        </text>
      </svg>
    ),
  },
  {
    id: "vertex",
    name: "Vertex",
    logo: (
      <svg
        className="h-8 w-auto fill-current"
        viewBox="0 0 130 32"
        xmlns="http://www.w3.org/2000/svg"
      >
        <path
          d="M16 2l14 24H2L16 2zm0 6L7 22h18L16 8z"
          className="text-violet-500 fill-current"
        />
        <text
          x="38"
          y="22"
          className="font-bold text-xl tracking-tight fill-current text-foreground"
        >
          VERTEX
        </text>
      </svg>
    ),
  },
  {
    id: "apex",
    name: "Apex",
    logo: (
      <svg
        className="h-8 w-auto fill-current"
        viewBox="0 0 120 32"
        xmlns="http://www.w3.org/2000/svg"
      >
        <rect
          x="4"
          y="4"
          width="20"
          height="20"
          rx="4"
          className="text-indigo-500 fill-current"
        />
        <path
          d="M10 18l3-3 3 3"
          stroke="white"
          strokeWidth="2"
          strokeLinecap="round"
          strokeLinejoin="round"
        />
        <text
          x="32"
          y="22"
          className="font-bold text-xl tracking-tight fill-current text-foreground"
        >
          APEX
        </text>
      </svg>
    ),
  },
  {
    id: "sovereign",
    name: "Sovereign",
    logo: (
      <svg
        className="h-8 w-auto fill-current"
        viewBox="0 0 160 32"
        xmlns="http://www.w3.org/2000/svg"
      >
        <path
          d="M16 2l6 6v10l-6 6-6-6V8l6-6z"
          className="text-rose-500 fill-current"
        />
        <text
          x="32"
          y="22"
          className="font-bold text-xl tracking-tight fill-current text-foreground"
        >
          SOVEREIGN
        </text>
      </svg>
    ),
  },
];

export interface LogoCloudsProps
  extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
  variant?: "marquee" | "grid" | "swap";
  logos?: Logo[];
  title?: React.ReactNode;
  subtitle?: React.ReactNode;
  speed?: number;
  direction?: "left" | "right";
  pauseOnHover?: boolean;
  gradient?: boolean;
  cols?: 2 | 3 | 4 | 5 | 6;
  swapCount?: number;
  interval?: number;
}

export function LogoClouds({
  variant = "grid",
  logos = DEFAULT_LOGOS,
  title,
  subtitle,
  speed = 40,
  direction = "left",
  pauseOnHover = true,
  gradient = true,
  cols = 4,
  swapCount = 4,
  interval = 3000,
  className,
  ...props
}: LogoCloudsProps) {
  const [batchIndex, setBatchIndex] = React.useState(0);

  const activeCount = Math.min(swapCount, logos.length);
  const totalBatches = Math.ceil(logos.length / activeCount);

  React.useEffect(() => {
    if (variant !== "swap") return;
    const timer = setInterval(() => {
      setBatchIndex((prev) => (prev + 1) % totalBatches);
    }, interval);
    return () => clearInterval(timer);
  }, [variant, totalBatches, interval]);

  const visibleLogos = React.useMemo(() => {
    if (variant !== "swap") return [];
    const startIdx = (batchIndex * activeCount) % logos.length;
    const slice = logos.slice(startIdx, startIdx + activeCount);
    if (slice.length < activeCount) {
      return [...slice, ...logos.slice(0, activeCount - slice.length)];
    }
    return slice;
  }, [variant, batchIndex, activeCount, logos]);

  const gridColsClass = {
    2: "grid-cols-2",
    3: "grid-cols-2 sm:grid-cols-3",
    4: "grid-cols-2 md:grid-cols-4",
    5: "grid-cols-2 sm:grid-cols-3 md:grid-cols-5",
    6: "grid-cols-2 sm:grid-cols-3 md:grid-cols-6",
  }[cols];

  return (
    <div
      className={cn("w-full py-12 px-4 sm:px-6 lg:px-8", className)}
      {...props}
    >
      {(title || subtitle) && (
        <div className="text-center mb-10 max-w-3xl mx-auto space-y-3">
          {title && (
            <h2 className="text-base font-semibold text-primary tracking-wide uppercase">
              {title}
            </h2>
          )}
          {subtitle && (
            <p className="text-2xl font-bold tracking-tight text-foreground sm:text-3xl">
              {subtitle}
            </p>
          )}
        </div>
      )}

      <div className="relative w-full">
        {variant === "marquee" && (
          <div
            className={cn(
              "w-full overflow-hidden",
              gradient &&
                "[mask-image:linear-gradient(to_right,transparent,white_15%,white_85%,transparent)]",
            )}
          >
            <Marquee
              speed={speed}
              direction={direction}
              pauseOnHover={pauseOnHover}
              className="flex items-center"
            >
              {logos.map((logo) => (
                <div
                  key={logo.id}
                  className="mx-8 flex items-center justify-center text-zinc-500 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors duration-200"
                >
                  {logo.logo}
                </div>
              ))}
            </Marquee>
          </div>
        )}

        {variant === "grid" && (
          <div
            className={cn(
              "grid gap-8 items-center justify-items-center",
              gridColsClass,
            )}
          >
            {logos.map((logo) => (
              <div
                key={logo.id}
                className="w-full flex items-center justify-center p-6 rounded-2xl bg-white dark:bg-neutral-900 border border-zinc-100 dark:border-neutral-800 text-zinc-500 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-100 transition-all duration-300 hover:shadow-md hover:border-zinc-200 dark:hover:border-neutral-700"
              >
                {logo.logo}
              </div>
            ))}
          </div>
        )}

        {variant === "swap" && (
          <div
            className={cn(
              "grid gap-8 items-center justify-items-center",
              {
                2: "grid-cols-2",
                3: "grid-cols-3",
                4: "grid-cols-2 md:grid-cols-4",
                5: "grid-cols-2 md:grid-cols-5",
                6: "grid-cols-3 md:grid-cols-6",
              }[Math.min(activeCount, 6) as 2 | 3 | 4 | 5 | 6],
            )}
          >
            {visibleLogos.map((logo, index) => (
              <div
                key={index}
                className="relative w-full h-24 flex items-center justify-center p-6 rounded-2xl bg-white dark:bg-neutral-900 border border-zinc-100 dark:border-neutral-800 text-zinc-500 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-100 transition-all duration-300 hover:shadow-md hover:border-zinc-200 dark:hover:border-neutral-700 overflow-hidden"
              >
                <AnimatePresence mode="wait">
                  <motion.div
                    key={logo.id}
                    initial={{ opacity: 0, y: 12, scale: 0.95 }}
                    animate={{ opacity: 1, y: 0, scale: 1 }}
                    exit={{ opacity: 0, y: -12, scale: 0.95 }}
                    transition={{ duration: 0.4, ease: "easeInOut" }}
                    className="absolute inset-0 flex items-center justify-center p-6"
                  >
                    {logo.logo}
                  </motion.div>
                </AnimatePresence>
              </div>
            ))}
          </div>
        )}
      </div>
    </div>
  );
}

Default

A clean, responsive grid layout for displaying client or partner logos. Each card features subtle shadows, borders, and hover scale transitions.

variant: marquee | grid | swapcols: numbertitle: stringsubtitle: string

TRUSTED BY THE BEST

Powering businesses of all sizes

DESIGNO
AETHEL
CRYPTON
NEXA
LUMINA
VERTEX
APEX
SOVEREIGN

Infinite Marquee Scroll

An infinite sliding marquee rendering partner logos with adjustable speed, pause on hover, and smooth css border gradients.

variant: marquee | grid | swapspeed: numberpauseOnHover: boolean

OUR PARTNERS

Integrates with your favorite tools

Batch Swap Animation

Transitions all cards concurrently at set intervals using a crossfade and y-axis translation spring effect.

variant: marquee | grid | swapswapCount: numberinterval: number

INNOVATING CONSTANTLY

Backed by industry leaders

DESIGNO
AETHEL
CRYPTON
NEXA

Minimal Fast Marquee

A lightweight, high-speed marquee with no title boundaries or styling constraints.

gradient: boolean
API Reference

Props — LogoClouds

Props for customizing the LogoClouds component.

PropTypeDefaultDescription
variant"marquee" | "grid" | "swap""grid"Exhibits marquee scroll, grid cards, or batch swap options.
speednumber40Speed multiplier for infinite marquee sliding motion.
swapCountnumber4Number of visible partner items shown at once in swap batch.
intervalnumber3000Cooldown timer miliseconds to transition next batch.
colsnumber4Number of columns in grid layout
titlestringundefinedSection heading label rendered above the grid
subtitlestringundefinedSupporting subtext rendered below the title
pauseOnHoverbooleanfalsePauses marquee motion when hovered
gradientbooleanfalseAdds fade edge gradient overlay to marquee edges