Skip to content
Black FridayBlack Friday launch price: Lifetime $149 until 30 Nov. Regular price $299 from 1 Dec.ends in 15d 03:13:36Get Lifetime →
loopkit
freeNew156 lines · framer-motion

Logo Cloud Marquee

A scrolling row of marks, drawn, not borrowed. An infinite horizontal marquee of abstract, hand-drawn SVG marks in bordered chips — a 'works with' row with none of the weight (or legal risk) of real logos.

Use it for
The social-proof row under a hero, before you have real integration or customer logos to show, or when you never want to depend on anyone else's brand assets.
Themes
Light + dark
License
Commercial use
Live

Works with

Plugs into the stack you already run

tsx
"use client";

import * as React from "react";
import { useReducedMotion } from "framer-motion";
import { cn } from "@/lib/utils";

function useReducedMotionSafe() {
  const prefersReduced = useReducedMotion();
  const [mounted, setMounted] = React.useState(false);
  React.useEffect(() => setMounted(true), []);
  return mounted ? !!prefersReduced : false;
}

export interface LogoMark {
  /** Which abstract SVG glyph to draw. */
  glyph: "orbit" | "prism" | "weave" | "node" | "arc" | "stack" | "spiral" | "flux";
  /** Relative width multiplier, 1 = default. */
  width?: number;
}

export interface LogoCloudMarqueeProps {
  eyebrow?: string;
  title?: string;
  logos?: LogoMark[];
  speedSeconds?: number;
  className?: string;
}

const DEFAULT_LOGOS: LogoMark[] = [
  { glyph: "orbit" },
  { glyph: "prism", width: 1.1 },
  { glyph: "weave" },
  { glyph: "node", width: 0.9 },
  { glyph: "arc" },
  { glyph: "stack", width: 1.05 },
  { glyph: "spiral" },
  { glyph: "flux", width: 0.95 },
];

/**
 * Each glyph is a fully abstract, hand-drawn mark — no letterforms, no resemblance
 * to any real or invented brand. They exist purely to fill a "works with" row with
 * motion and shape instead of grey boxes.
 */
function Glyph({ kind }: { kind: LogoMark["glyph"] }) {
  const common = { viewBox: "0 0 32 32", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round" as const, strokeLinejoin: "round" as const };
  switch (kind) {
    case "orbit":
      return (
        <svg {...common} className="size-7">
          <circle cx="16" cy="16" r="3.2" fill="currentColor" stroke="none" />
          <ellipse cx="16" cy="16" rx="13" ry="6" />
          <ellipse cx="16" cy="16" rx="6" ry="13" transform="rotate(35 16 16)" />
        </svg>
      );
    case "prism":
      return (
        <svg {...common} className="size-7">
          <path d="M16 4 27 23H5Z" />
          <path d="M16 4 16 17M27 23 16 17M5 23 16 17" />
        </svg>
      );
    case "weave":
      return (
        <svg {...common} className="size-7">
          <path d="M4 10c5 0 5 6 10 6s5-6 10-6" />
          <path d="M4 16c5 0 5 6 10 6s5-6 10-6" />
        </svg>
      );
    case "node":
      return (
        <svg {...common} className="size-7">
          <circle cx="8" cy="9" r="2.6" />
          <circle cx="24" cy="9" r="2.6" />
          <circle cx="16" cy="23" r="2.6" />
          <path d="M10.3 10.6 13.8 20.6M21.7 10.6 18.2 20.6M10.6 9h10.8" />
        </svg>
      );
    case "arc":
      return (
        <svg {...common} className="size-7">
          <path d="M6 23c0-9 4.5-15 10-15s10 6 10 15" />
          <path d="M11 23c0-6 2.5-10 5-10s5 4 5 10" />
        </svg>
      );
    case "stack":
      return (
        <svg {...common} className="size-7">
          <path d="m16 5 11 5.5L16 16 5 10.5Z" />
          <path d="m5 17 11 5.5L27 17M5 23.5 16 29l11-5.5" />
        </svg>
      );
    case "spiral":
      return (
        <svg {...common} className="size-7">
          <path d="M16 16c3 0 5-2 5-5s-3-5-6-5-7 2.5-7 7 3.5 8 8 8 9-3.5 9-9" />
        </svg>
      );
    case "flux":
      return (
        <svg {...common} className="size-7">
          <path d="M6 10h11a5 5 0 0 1 0 10H9" />
          <path d="M26 22H15a5 5 0 0 1 0-10h8" />
          <path d="m9 7-3 3 3 3M23 19l3 3-3 3" />
        </svg>
      );
  }
}

function LogoChip({ glyph, width = 1 }: LogoMark) {
  return (
    <div
      className="flex h-14 shrink-0 items-center justify-center rounded-xl border border-border bg-muted/40 px-6 text-muted-foreground transition-colors hover:border-foreground/40 hover:text-foreground"
      style={{ width: `${7.5 * width}rem` }}
    >
      <Glyph kind={glyph} />
    </div>
  );
}

export function LogoCloudMarquee({ eyebrow = "Works with", title = "Plugs into the stack you already run", logos = DEFAULT_LOGOS, speedSeconds = 30, className }: LogoCloudMarqueeProps) {
  const reduce = useReducedMotionSafe();
  const doubled = [...logos, ...logos];

  return (
    <section className={cn("w-full overflow-hidden bg-background py-16", className)}>
      <div className="mx-auto max-w-3xl px-4 text-center sm:px-6 lg:px-10">
        <p className="font-mono text-[11px] uppercase tracking-[0.2em] text-muted-foreground">{eyebrow}</p>
        <h2 className="mt-3 font-display text-[clamp(1.6rem,3.6vw,2.4rem)] font-extrabold leading-[0.95] tracking-[-0.03em] text-foreground">{title}</h2>
      </div>

      <div className="group relative mt-10 overflow-hidden [mask-image:linear-gradient(90deg,transparent,black_8%,black_92%,transparent)]">
        <div
          className="flex w-max items-center gap-4 [animation-name:lk-logo-marquee] [animation-timing-function:linear] [animation-iteration-count:infinite] group-hover:[animation-play-state:paused] motion-reduce:[animation-play-state:paused]"
          style={{ animationDuration: `${speedSeconds}s` }}
        >
          {doubled.map((l, i) => (
            <LogoChip key={i} {...l} />
          ))}
        </div>
      </div>

      {!reduce && (
        <style>{`
          @keyframes lk-logo-marquee {
            from { transform: translateX(0%); }
            to { transform: translateX(-50%); }
          }
        `}</style>
      )}
    </section>
  );
}

export default LogoCloudMarquee;

How it behaves

  • →Eight distinct abstract line-art glyphs (orbit, prism, weave, node, arc, stack, spiral, flux) — no letterforms, no resemblance to any real or invented brand.
  • →The row duplicates its items and scrolls on a CSS keyframe animation at a constant speed set by speedSeconds.
  • →The row pauses on hover and is masked with a fade at both edges so items never pop in or out abruptly.
  • →prefers-reduced-motion pauses the scroll entirely, leaving a static row.

Accessibility

  • ✓Marks are purely decorative; the section heading carries the meaning.
  • ✓Scrolling pauses under prefers-reduced-motion and on hover/focus-within.
  • ✓Honours prefers-reduced-motion.