Web InnoventixFreeCode

Logos Marquee

Original · free

logo marquee (generic marks)

byWeb InnoventixReact + Tailwind
mqxlogosmarquees
Open

Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.

npx shadcn@latest add https://webinnoventix.com/r/mqx-logos.json
mqx-logos.tsx
"use client";

import { useId, useState, type CSSProperties, type ReactNode } from "react";
import { useReducedMotion } from "motion/react";

type Speed = "slow" | "normal" | "fast";

type Logo = { name: string; mark: ReactNode };

const SPEEDS: { id: Speed; label: string }[] = [
  { id: "slow", label: "Slow" },
  { id: "normal", label: "Normal" },
  { id: "fast", label: "Fast" },
];

const DURATION: Record<Speed, { a: number; b: number }> = {
  slow: { a: 82, b: 98 },
  normal: { a: 50, b: 62 },
  fast: { a: 28, b: 36 },
};

const LOGOS: Logo[] = [
  {
    name: "Northwind",
    mark: (
      <svg viewBox="0 0 24 24" width="22" height="22" fill="none" aria-hidden="true" focusable="false">
        <circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="1.5" />
        <path d="M12 6l2.2 3.8L18 12l-3.8 2.2L12 18l-2.2-3.8L6 12l3.8-2.2L12 6Z" fill="currentColor" />
      </svg>
    ),
  },
  {
    name: "Lumenire",
    mark: (
      <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" aria-hidden="true" focusable="false">
        <circle cx="12" cy="12" r="3.8" />
        <path d="M12 2.5v3M12 18.5v3M2.5 12h3M18.5 12h3M5 5l2.1 2.1M16.9 16.9 19 19M19 5l-2.1 2.1M7.1 16.9 5 19" />
      </svg>
    ),
  },
  {
    name: "Vaultly",
    mark: (
      <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" aria-hidden="true" focusable="false">
        <path d="M12 3 5 6v5c0 4.5 3 7.6 7 9 4-1.4 7-4.5 7-9V6l-7-3Z" />
        <circle cx="12" cy="11" r="1.6" fill="currentColor" stroke="none" />
        <path d="M12 12.6V15" strokeLinecap="round" />
      </svg>
    ),
  },
  {
    name: "Cascade",
    mark: (
      <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
        <path d="m5 6 7 5 7-5M5 11l7 5 7-5M5 16l7 5 7-5" />
      </svg>
    ),
  },
  {
    name: "Orbit Labs",
    mark: (
      <svg viewBox="0 0 24 24" width="22" height="22" fill="none" aria-hidden="true" focusable="false">
        <circle cx="12" cy="12" r="2.3" fill="currentColor" />
        <ellipse cx="12" cy="12" rx="9" ry="3.9" stroke="currentColor" strokeWidth="1.5" />
        <ellipse cx="12" cy="12" rx="9" ry="3.9" stroke="currentColor" strokeWidth="1.5" transform="rotate(60 12 12)" />
      </svg>
    ),
  },
  {
    name: "Halcyon",
    mark: (
      <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
        <path d="M3 15c4 0 6.2-2.2 9-6 2.8 3.8 5 6 9 6" />
        <path d="M12 9.2V18" opacity=".55" />
      </svg>
    ),
  },
  {
    name: "Quanta",
    mark: (
      <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" aria-hidden="true" focusable="false">
        <path d="M12 3 20 7.5v9L12 21 4 16.5v-9L12 3Z" />
        <circle cx="12" cy="12" r="2" fill="currentColor" stroke="none" />
      </svg>
    ),
  },
  {
    name: "Meridian",
    mark: (
      <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="1.5" aria-hidden="true" focusable="false">
        <circle cx="12" cy="12" r="9" />
        <path d="M3 12h18M12 3c3 3 3 15 0 18M12 3c-3 3-3 15 0 18" />
      </svg>
    ),
  },
  {
    name: "Solstice",
    mark: (
      <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" aria-hidden="true" focusable="false">
        <path d="M4 16a8 8 0 0 1 16 0" />
        <path d="M7 16a5 5 0 0 1 10 0" />
        <circle cx="12" cy="16" r="1.4" fill="currentColor" stroke="none" />
      </svg>
    ),
  },
  {
    name: "Ironwood",
    mark: (
      <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" aria-hidden="true" focusable="false">
        <path d="M12 3 6 12h3l-3 5h12l-3-5h3L12 3Z" />
        <path d="M12 17v4" strokeLinecap="round" />
      </svg>
    ),
  },
  {
    name: "Nimbus",
    mark: (
      <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" aria-hidden="true" focusable="false">
        <path d="M7 18a4 4 0 0 1 0-8 5 5 0 0 1 9.6-1.3A3.5 3.5 0 0 1 17 18H7Z" />
      </svg>
    ),
  },
  {
    name: "Fathom",
    mark: (
      <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
        <circle cx="12" cy="4.6" r="1.8" />
        <path d="M12 6.4V20M8.5 10h7M5 13a7 7 0 0 0 14 0M5 13h2M17 13h2" />
      </svg>
    ),
  },
];

function pillMarkup(logos: Logo[], keyPrefix: string): ReactNode {
  return logos.map((logo) => (
    <li
      key={`${keyPrefix}-${logo.name}`}
      className="mx-3 flex items-center gap-2.5 whitespace-nowrap rounded-xl border border-slate-200/80 bg-white/70 px-5 py-3 shadow-sm backdrop-blur-sm transition-colors duration-200 hover:border-indigo-300 hover:bg-white dark:border-slate-700/70 dark:bg-slate-900/50 dark:hover:border-indigo-500/60 dark:hover:bg-slate-900"
    >
      <span className="text-slate-400 dark:text-slate-500">{logo.mark}</span>
      <span className="text-[15px] font-semibold tracking-tight text-slate-700 dark:text-slate-200">
        {logo.name}
      </span>
    </li>
  ));
}

export default function MqxLogos() {
  const uid = useId();
  const reduce = useReducedMotion() ?? false;
  const [isPlaying, setIsPlaying] = useState(true);
  const [speed, setSpeed] = useState<Speed>("normal");

  const { a: durA, b: durB } = DURATION[speed];
  const rowB = [...LOGOS].reverse();

  return (
    <section className="relative w-full overflow-hidden bg-gradient-to-b from-white to-slate-50 px-6 py-20 dark:from-slate-950 dark:to-slate-900 sm:py-28">
      <style>{`
        @keyframes mqxlogos-marquee-a { from { transform: translateX(0); } to { transform: translateX(-50%); } }
        @keyframes mqxlogos-marquee-b { from { transform: translateX(-50%); } to { transform: translateX(0); } }
        .mqxlogos-track {
          animation-duration: var(--mqxlogos-dur, 50s);
          animation-timing-function: linear;
          animation-iteration-count: infinite;
          will-change: transform;
        }
        .mqxlogos-track--a { animation-name: mqxlogos-marquee-a; }
        .mqxlogos-track--b { animation-name: mqxlogos-marquee-b; }
        .mqxlogos-viewport[data-playing="false"] .mqxlogos-track { animation-play-state: paused; }
        .mqxlogos-viewport:hover .mqxlogos-track { animation-play-state: paused; }
        .mqxlogos-viewport:focus-within .mqxlogos-track { animation-play-state: paused; }
        .mqxlogos-fade {
          -webkit-mask-image: linear-gradient(to right, transparent, #000 7%, #000 93%, transparent);
          mask-image: linear-gradient(to right, transparent, #000 7%, #000 93%, transparent);
        }
        @media (prefers-reduced-motion: reduce) {
          .mqxlogos-track { animation: none !important; transform: none !important; }
        }
      `}</style>

      <div className="mx-auto max-w-6xl">
        <div className="flex flex-col items-start gap-8 sm:flex-row sm:items-end sm:justify-between">
          <div className="max-w-xl">
            <p className="text-xs font-semibold uppercase tracking-[0.22em] text-indigo-600 dark:text-indigo-400">
              Our customers
            </p>
            <h2 className="mt-3 text-3xl font-semibold tracking-tight text-slate-900 dark:text-white sm:text-4xl">
              The companies building what&rsquo;s next
            </h2>
            <p className="mt-4 text-base leading-relaxed text-slate-600 dark:text-slate-400">
              From seed-stage startups to public enterprises, more than 40,000 teams
              ship on our platform every week.
            </p>
          </div>

          <div className="flex flex-wrap items-center gap-3">
            <button
              type="button"
              onClick={() => setIsPlaying((v) => !v)}
              aria-pressed={isPlaying}
              aria-label={isPlaying ? "Pause logo animation" : "Play logo animation"}
              className="inline-flex items-center gap-2 rounded-full border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-sm transition-colors hover:border-slate-400 hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:hover:border-slate-600 dark:hover:bg-slate-800 dark:focus-visible:ring-offset-slate-950"
            >
              {isPlaying ? (
                <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true" focusable="false">
                  <rect x="6.5" y="5" width="3.5" height="14" rx="1" />
                  <rect x="14" y="5" width="3.5" height="14" rx="1" />
                </svg>
              ) : (
                <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true" focusable="false">
                  <path d="M8 5.2v13.6a1 1 0 0 0 1.53.85l10.6-6.8a1 1 0 0 0 0-1.7L9.53 4.35A1 1 0 0 0 8 5.2Z" />
                </svg>
              )}
              {isPlaying ? "Pause" : "Play"}
            </button>

            <fieldset className="m-0 border-0 p-0">
              <legend className="sr-only">Marquee speed</legend>
              <div className="inline-flex rounded-full border border-slate-300 bg-white p-1 shadow-sm dark:border-slate-700 dark:bg-slate-900">
                {SPEEDS.map((s) => (
                  <label key={s.id} className="cursor-pointer">
                    <input
                      type="radio"
                      name={`${uid}-speed`}
                      value={s.id}
                      checked={speed === s.id}
                      onChange={() => setSpeed(s.id)}
                      className="sr-only peer"
                    />
                    <span className="block rounded-full px-3.5 py-1.5 text-sm font-medium text-slate-600 transition-colors peer-checked:bg-indigo-600 peer-checked:text-white peer-focus-visible:outline-none peer-focus-visible:ring-2 peer-focus-visible:ring-indigo-500 peer-focus-visible:ring-offset-2 peer-focus-visible:ring-offset-white dark:text-slate-300 dark:peer-focus-visible:ring-offset-slate-950">
                      {s.label}
                    </span>
                  </label>
                ))}
              </div>
            </fieldset>
          </div>
        </div>

        <ul className="sr-only">
          {LOGOS.map((logo) => (
            <li key={`sr-${logo.name}`}>{logo.name}</li>
          ))}
        </ul>

        {reduce ? (
          <p className="mt-4 text-sm text-slate-500 dark:text-slate-500">
            Motion is reduced in your system settings, so the logos are shown without
            scrolling.
          </p>
        ) : null}

        <div
          className="mqxlogos-viewport mqxlogos-fade mt-12 space-y-5"
          data-playing={isPlaying ? "true" : "false"}
          aria-hidden="true"
        >
          <div className="overflow-hidden">
            <div
              className="mqxlogos-track mqxlogos-track--a flex w-max"
              style={{ ["--mqxlogos-dur"]: `${durA}s` } as CSSProperties}
            >
              <ul className="flex w-max shrink-0 items-center">{pillMarkup(LOGOS, "a1")}</ul>
              <ul className="flex w-max shrink-0 items-center">{pillMarkup(LOGOS, "a2")}</ul>
            </div>
          </div>

          <div className="overflow-hidden">
            <div
              className="mqxlogos-track mqxlogos-track--b flex w-max"
              style={{ ["--mqxlogos-dur"]: `${durB}s` } as CSSProperties}
            >
              <ul className="flex w-max shrink-0 items-center">{pillMarkup(rowB, "b1")}</ul>
              <ul className="flex w-max shrink-0 items-center">{pillMarkup(rowB, "b2")}</ul>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

Dependencies

motion

Licence

Built by Web Innoventix. Free for personal and commercial use, no attribution required.

Built by Web Innoventix

Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.

Get a free quote

Similar components

Browse all →