Lunia DS

Marketing

Logo marquee

A strip of customer logos that moves past at a steady speed with soft faded edges, slows to a stop when you point at it, and keeps the logo under the pointer bright while the others dim.

Loading preview

Eight made-up company wordmarks drawn as simple inline SVG, moving left at 36px per second across a 640px wide card. Pointing at the strip slows it to a stop and brightens the logo under the pointer; Replay restarts the loop from the first logo.

Installation

Run this in your app after the one-time setup on the Getting started page.

$ npx @ganisigit/lunia-ds add logo-marquee

Usage

The demo above, as code.

demo.tsx
'use client';

import type * as React from 'react';
import { type Logo, LogoMarquee } from './logo-marquee';

const LOGOS: Logo[] = [
  { name: 'Orbitly' },
  { name: 'Quillnest' },
  { name: 'Fernwave' },
  { name: 'Brightloom' },
  { name: 'Tidepoint' },
  { name: 'Mintforge' },
  { name: 'Zephro' },
  { name: 'Oakline' },
];

// Simple invented marks, drawn with currentColor so they follow the strip's dimming.
const MARKS: React.ReactNode[] = [
  <circle key="a" cx="12" cy="12" r="8" fill="none" stroke="currentColor" strokeWidth="3" />,
  <path key="b" d="M4 18 12 4l8 14z" fill="currentColor" />,
  <path
    key="c"
    d="M3 12c3-6 6-6 9 0s6 6 9 0"
    fill="none"
    stroke="currentColor"
    strokeWidth="3"
    strokeLinecap="round"
  />,
  <rect key="d" x="4" y="4" width="16" height="16" rx="5" fill="currentColor" />,
  <path
    key="e"
    d="M12 3 21 12 12 21 3 12z"
    fill="none"
    stroke="currentColor"
    strokeWidth="3"
    strokeLinejoin="round"
  />,
  <path key="f" d="M4 6h16M4 12h10M4 18h16" stroke="currentColor" strokeWidth="3" strokeLinecap="round" />,
  <path key="g" d="M5 5l14 14M19 5 5 19" stroke="currentColor" strokeWidth="3" strokeLinecap="round" />,
  <path key="h" d="M12 3c5 5 5 13 0 18-5-5-5-13 0-18z" fill="currentColor" />,
];

function wordmark(logo: Logo, index: number) {
  return (
    <span role="img" aria-label={logo.name} className="flex items-center gap-1.5 text-lg font-semibold">
      <svg aria-hidden="true" viewBox="0 0 24 24" className="size-5">
        {MARKS[index % MARKS.length]}
      </svg>
      <span aria-hidden="true">{logo.name}</span>
    </span>
  );
}

export default function LogoMarqueeDemo({ replay = 0 }: { replay?: number }) {
  return (
    <div className="w-full max-w-[640px] rounded-xl border border-secondary bg-primary">
      {/* Replay mounts the strip again, so the loop starts from the first logo. */}
      <LogoMarquee key={replay} logos={LOGOS} renderLogo={wordmark} />
    </div>
  );
}

API

Parts: root (clips the strip, fades both edges with a mask), track (two copies of the logo set, side by side), logo list, logo item, copy list (aria-hidden), static row (reduced motion: wrapped and centered).

PropTypeDefaultDescription
logosLogo[]-The logos, each { name, src?, width?, height? }; name is the accessible name. Use the logo slot for inline SVG. Required.
speednumber36Speed in px per second, so a longer strip does not move faster.
gapnumber56Space between logos in px.
direction"left" | "right""left"Direction the logos move.
pauseOnHoverbooleantrueSlow to a stop while the pointer or keyboard focus is on the strip.
SlotDescription
logoScoped slot with { logo, index } to render a logo yourself, for example an inline SVG that follows the text color.

Motion

Every row names a token from the Motion page. The last column says what happens when the user asks for reduced motion.

IDTriggerElementPropertyFrom → toTimingReduced motion
LMQ1mounted (loops forever)tracktransform translateX0 → -50% (one logo set)prop:speed (36)ease-linearNo loop
LMQ2pointer enters the strip, or focus moves inside ittrackplayback rate1 → 0duration-5xl (640)ease-outStays still
LMQ3pointer leaves the strip, or focus leaves ittrackplayback rate0 → 1duration-5xl (640)ease-outStays still
LMQ4pointer enters the striplogo items not under the pointeropacity1 → 0.35duration-4xl (520)ease-outStays still
LMQ5pointer on a logologo itemopacity + color0.35, text-tertiary → 1, text-primaryduration-4xl (520)ease-outStays still

Accessibility

  • The logos are a list; each logo has an accessible name (alt text or the name field).
  • Only the first logo set is exposed; the second copy that makes the loop seamless is aria-hidden.
  • The strip slows to a stop on keyboard focus inside it as well as on hover; the source reacts to the pointer only.
  • Reduced motion: the strip does not move at all; the logos sit in a wrapped, centered row with the same gap, and there is no dimming.