---
title: "How to Create an Infinite Logo Marquee in Framer"
description: "Build an infinite logo marquee in Framer with a seamless CSS loop — the working code, pause-on-hover, edge fades, and a ready-made component option."
canonical_url: "https://framerhub.io/blog/infinite-marquee-framer"
last_updated: "2027-02-05T00:00:00.000Z"
---

The infinite logo marquee — a row of client or "as seen in" logos scrolling continuously and seamlessly sideways — is one of the most common trust-building patterns on SaaS and agency sites, and one of the most frequently implemented with a visible, jarring jump at the loop point. Here's how to build the seamless version properly, plus the pause-on-hover and edge-fade details that separate a polished marquee from a rough one.

## The Seamless Loop Trick

The core technique behind a jump-free marquee is simple but easy to get wrong: **duplicate the full set of logos** inside the scrolling track (at least once, sometimes twice for shorter logo sets), then animate the track's horizontal position by exactly the width of one full set — typically `-50%` of the total track width for a two-copy setup. Because the second copy is pixel-identical to the first, the moment the animation resets back to `0%`, what's on screen is indistinguishable from what was there a frame earlier. The loop is invisible not because of clever easing, but because the content itself repeats exactly.

Get the duplication or the percentage wrong — duplicating only part of the set, or animating by a distance that doesn't match a full copy's width — and you get a visible stutter or jump at the loop point, which is the single most common marquee bug.

This is worth spelling out because a lot of quick tutorials skip straight to "just use a CSS animation" without explaining why the duplication step is what actually makes the loop invisible — the animation itself is trivial (a linear, infinitely repeating horizontal translate). The entire visual quality of the effect lives in getting the content duplication and the offset percentage to match exactly, which is also exactly the part that's easy to get subtly wrong when logos are added or removed later and the duplication logic isn't re-derived from the new count.

## Why Logo Marquees Work as a Trust Signal

A row of recognizable client or press logos is one of the fastest, lowest-effort ways to borrow credibility on a landing page — a visitor doesn't need to read anything to register "companies I've heard of use or cover this," which is a meaningfully different, faster kind of trust signal than a written testimonial or case study requires. The continuous motion adds a small amount of additional perceived activity and momentum to the section without demanding conscious attention, which is part of why it's become such a standard placement directly below a hero section: it reinforces credibility in the handful of seconds a visitor spends deciding whether to keep scrolling.

## Building It With a Code Override

```jsx
import { motion } from "motion/react"
import { useState } from "react"
import { addPropertyControls, ControlType } from "framer"

export default function LogoMarquee(props) {
  const { logos, speed, pauseOnHover, gap, logoHeight, edgeFade } = props
  const [isPaused, setIsPaused] = useState(false)
  const list = logos && logos.length ? logos : []

  return (
    <div
      style={{
        position: "relative",
        overflow: "hidden",
        width: "100%",
        maskImage: edgeFade
          ? "linear-gradient(90deg, transparent, black 10%, black 90%, transparent)"
          : "none",
        WebkitMaskImage: edgeFade
          ? "linear-gradient(90deg, transparent, black 10%, black 90%, transparent)"
          : "none",
      }}
      onMouseEnter={() => pauseOnHover && setIsPaused(true)}
      onMouseLeave={() => pauseOnHover && setIsPaused(false)}
    >
      <motion.div
        style={{ display: "flex", gap, width: "max-content" }}
        animate={{ x: isPaused ? undefined : ["0%", "-50%"] }}
        transition={{ duration: speed, ease: "linear", repeat: Infinity }}
      >
        {[...list, ...list].map((logo, i) => (
          <img
            key={i}
            src={logo}
            alt=""
            style={{ height: logoHeight, width: "auto", flexShrink: 0, objectFit: "contain" }}
          />
        ))}
      </motion.div>
    </div>
  )
}

addPropertyControls(LogoMarquee, {
  logos: { type: ControlType.Array, control: { type: ControlType.Image } },
  speed: { type: ControlType.Number, defaultValue: 25, min: 8, max: 60 },
  pauseOnHover: { type: ControlType.Boolean, defaultValue: true },
  gap: { type: ControlType.Number, defaultValue: 48, min: 8, max: 120 },
  logoHeight: { type: ControlType.Number, defaultValue: 32, min: 16, max: 80 },
  edgeFade: { type: ControlType.Boolean, defaultValue: true },
})
```

A few details in this implementation worth understanding:

- **[...list, ...list]** is the actual duplication step — the track renders every logo twice in sequence, back to back, which is what makes the `-50%` animation loop seamlessly.
- **width: "max-content"** on the animated track keeps it exactly as wide as its content requires, rather than constrained to the container's width — critical, since the `-50%` offset needs to correspond to exactly one full logo set's width, which only works if the track isn't being artificially compressed or stretched.
- **The maskImage/WebkitMaskImage gradient** produces the edge-fade effect, fading logos to transparent at the left and right edges of the container rather than cutting off abruptly — this is a CSS mask, not a separate image asset, and it automatically adapts to whatever background color or image sits behind the marquee.
- **animate={{ x: isPaused ? undefined : [...] }}** is the pause-on-hover mechanism — setting the animated value to `undefined` while paused stops the animation exactly where it was, rather than resetting or jumping, so resuming feels continuous rather than restarting from scratch.

## Tuning Speed and Spacing

- **Speed (duration in seconds for one full loop):** Lower values move faster. 20-30 seconds per loop is a comfortable, easily-scannable pace for most logo counts; below 15 seconds starts to feel rushed, particularly if visitors are meant to actually register individual logos rather than just perceive general motion.
- **Gap:** Generous spacing (40-60px) between logos reads as more premium than a tightly packed row — resist the urge to fit more logos in by shrinking the gap, since a cramped marquee looks busier and cheaper than a spacious one with fewer, more legible logos.
- **Logo height consistency:** Normalize all logos to the same height (not necessarily the same width, since logo aspect ratios vary widely) so the row reads as a cohesive set rather than a jumble of mismatched sizes.

## Pause on Hover: Worth the Extra Interaction

Pausing the marquee on hover is a small addition with real UX payoff — it gives a visitor who recognizes a specific client logo, or wants to actually read a smaller wordmark, a chance to stop the motion and look. Without it, a visitor genuinely interested in a specific logo has to "chase" it with their eyes as it scrolls past, which is a frustrating amount of friction for what should be a low-effort trust signal. The tradeoff: pause-on-hover only works for pointer-driven devices, so it's purely a desktop enhancement — on mobile, there's no hover state to pause with, which is fine, since the marquee is already easier to glance at on a smaller, more focused viewport.

## Drop-In Options

If you'd rather not hand-build and tune the loop math yourself, FramerHub's [InfiniteScrollingTicker component](/components/infinite-scrolling-ticker) implements exactly this pattern — seamless looping, pause-on-hover, spacing, and speed all exposed as property panel controls. For a related but visually different pattern — a scrolling gallery with parallax depth and snapping physics rather than a flat logo row — [InfiniteGallery](/components/infinite-gallery) covers that heavier, more dimensional variant, which suits an image or project showcase better than a simple logo strip. Both are part of FramerHub's [component library](/components), accessible through the [Components plugin](/plugins/framer-components).

## Common Mistakes

- **Duplicating logos an odd or mismatched number of times.** The animated offset (`-50%` for two copies) has to correspond exactly to how many times the set is duplicated — get this out of sync and you get a visible stutter at the loop point.
- **Fixed-width containers on the track.** As noted above, the track needs `width: max-content` (or equivalent) so it's exactly as wide as its actual content, not artificially constrained by its parent.
- **Inconsistent logo file sizes and formats.** Mixing raster PNGs at wildly different resolutions with vector SVGs produces visibly inconsistent sharpness across the row — standardize on SVG where possible, or export raster logos at consistent, sufficiently high resolution.
- **No edge fade over a non-transparent background.** Without the mask gradient, logos abruptly appear and disappear at the container's edges rather than fading in — a small detail, but one that reads as noticeably more polished once you've seen it done correctly.

## Multi-Row and Reverse-Direction Variants

Once the single-row loop is solid, a few variations extend the same technique without adding much complexity:

- **Two rows moving in opposite directions.** Stack a second marquee track directly below the first, with its animation running in reverse (`["-50%", "0%"]` instead of `["0%", "-50%"]`). This is a common pattern for sites with a larger logo set than fits comfortably in one row, and the opposing motion reads as more dynamic than two rows moving the same direction.
- **Different speeds per row.** If you do run two rows, giving them slightly different speeds (rather than identical timing) avoids the two rows visually syncing up and drifting in and out of alignment in a way that can look accidental rather than designed.
- **Grayscale-to-color on hover per logo.** A common refinement: render logos in grayscale at rest and transition to full color on hover (per individual logo, using a CSS filter transition), giving the row a more unified, understated look until a visitor's cursor calls out a specific one.

## Placement on the Page

Logo marquees most commonly sit directly below the hero section, functioning as an immediate credibility follow-up to whatever claim the hero headline just made. They also work well directly above a testimonials or case-study section, functioning as a lighter-weight lead-in before the more detailed proof that follows. Avoid placing a marquee too far down a long page — its value is largely in reinforcing early trust while a visitor is still deciding whether to keep reading, not as a mid-page or footer element where it's more likely to be skimmed past entirely.

## FAQ

**How do you make a logo marquee loop seamlessly with no visible jump?**
Duplicate the full logo set at least once inside the scrolling track, then animate the track exactly -50% of its total width (for a two-copy track) using a linear, infinitely repeating transition. Because the second copy is identical to the first, the loop point is invisible.

**Should a logo marquee pause on hover?**
Yes, for most "as seen in" or client-logo marquees — pausing on hover lets a visitor stop the motion to actually read a specific logo they recognize or want a closer look at, which a continuously moving marquee doesn't allow.

**How many logos do I need for a smooth marquee?**
6 to 12 logos is the practical range for most marquees. Fewer than 6 and the loop repeats too quickly to feel like a continuous stream; more than 12 and the track gets long enough that a visitor may only see a handful of logos before scrolling past the section entirely.

**Should logo marquee speed be the same across desktop and mobile?**
Generally yes in terms of pixels-per-second, but test specifically on a narrow mobile viewport — the same speed can feel faster on a smaller screen where a logo occupies more of the visible width, so a slight reduction for mobile is sometimes worth tuning in separately.

**Do edge fade gradients require extra images?**
No — the fade is a CSS mask-image or a layered gradient-colored overlay on top of the marquee track, not a separate asset. It reads as the logos fading into the page background at each edge of the container.
