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.
Live preview
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-marqueeUsage
The demo above, as code.
'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).
| Prop | Type | Default | Description |
|---|---|---|---|
| logos | Logo[] | - | The logos, each { name, src?, width?, height? }; name is the accessible name. Use the logo slot for inline SVG. Required. |
| speed | number | 36 | Speed in px per second, so a longer strip does not move faster. |
| gap | number | 56 | Space between logos in px. |
| direction | "left" | "right" | "left" | Direction the logos move. |
| pauseOnHover | boolean | true | Slow to a stop while the pointer or keyboard focus is on the strip. |
| Slot | Description |
|---|---|
| logo | Scoped 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.
| ID | Trigger | Element | Property | From → to | Timing | Reduced motion |
|---|---|---|---|---|---|---|
| LMQ1 | mounted (loops forever) | track | transform translateX | 0 → -50% (one logo set) | prop:speed (36)ease-linear | No loop |
| LMQ2 | pointer enters the strip, or focus moves inside it | track | playback rate | 1 → 0 | duration-5xl (640)ease-out | Stays still |
| LMQ3 | pointer leaves the strip, or focus leaves it | track | playback rate | 0 → 1 | duration-5xl (640)ease-out | Stays still |
| LMQ4 | pointer enters the strip | logo items not under the pointer | opacity | 1 → 0.35 | duration-4xl (520)ease-out | Stays still |
| LMQ5 | pointer on a logo | logo item | opacity + color | 0.35, text-tertiary → 1, text-primary | duration-4xl (520)ease-out | Stays 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.