12 CSS Infinite Marquee Designs02 / 12

Pure CSSMIT licensed

B2B Client Logo Wall — Two-Row Counter-Scrolling Marquee

An agency-grade client wall: two logo rows gliding in opposite directions at slightly different speeds, wrapped in badge-style logo cards that lift on hover while the whole wall pauses — the counter-motion reads as 'lots of clients' at a glance without a single line of JavaScript.

Published

Live Demo
Try it

The code

<section class="mqs-02" aria-label="Client logo wall marquee demo">
  <div class="mqs-02__inner">
    <h3 class="mqs-02__title">Trusted by 200+ product teams</h3>
    <div class="mqs-02__wall">
      <div class="mqs-02__viewport">
        <div class="mqs-02__group" style="--speed:30s">
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#2563eb"></span>Northbeam</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#dc2626"></span>Cartload</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#059669"></span>Fernwork</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#7c3aed"></span>Quill &amp; Co</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#ea580c"></span>Baseline</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#0891b2"></span>Harbourly</span>
        </div>
        <div class="mqs-02__group" style="--speed:30s" aria-hidden="true">
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#2563eb"></span>Northbeam</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#dc2626"></span>Cartload</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#059669"></span>Fernwork</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#7c3aed"></span>Quill &amp; Co</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#ea580c"></span>Baseline</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#0891b2"></span>Harbourly</span>
        </div>
      </div>
      <div class="mqs-02__viewport">
        <div class="mqs-02__group mqs-02__group--rev" style="--speed:38s">
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#be185d"></span>Alpenglow</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#4d7c0f"></span>Statfield</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#b45309"></span>Mooring</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#1d4ed8"></span>Deltaform</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#0f766e"></span>Palisade</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#9333ea"></span>Winward</span>
        </div>
        <div class="mqs-02__group mqs-02__group--rev" style="--speed:38s" aria-hidden="true">
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#be185d"></span>Alpenglow</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#4d7c0f"></span>Statfield</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#b45309"></span>Mooring</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#1d4ed8"></span>Deltaform</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#0f766e"></span>Palisade</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#9333ea"></span>Winward</span>
        </div>
      </div>
    </div>
  </div>
</section>
.mqs-02,
.mqs-02 *,
.mqs-02 *::before,
.mqs-02 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.mqs-02 {
  min-height: 100vh;
  width: 100%;
  --gap: 20px;
  font-family: 'Segoe UI',system-ui,sans-serif;
  background: #0f172a;
  padding: 64px 24px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.mqs-02__inner {
  width: min(960px,100%);
}

.mqs-02__title {
  font-size: clamp(18px,3vw,24px);
  font-weight: 700;
  color: #e2e8f0;
  text-align: center;
  margin-bottom: 34px;
  letter-spacing: -.01em;
}

.mqs-02__wall {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
}

.mqs-02__viewport {
  display: flex;
  gap: var(--gap);
  overflow: hidden;
  mask-image: linear-gradient(90deg,transparent,#000 10%,#000 90%,transparent);
  -webkit-mask-image: linear-gradient(90deg,transparent,#000 10%,#000 90%,transparent);
}

.mqs-02__group {
  flex: none;
  display: flex;
  gap: var(--gap);
  animation: mqs-02-scroll var(--speed,30s) linear infinite;
  will-change: transform;
}

.mqs-02__group--rev {
  animation-direction: reverse;
}

@keyframes mqs-02-scroll {
  to {
    transform: translateX(calc(-100% - var(--gap)));
  }
}

.mqs-02__wall:hover .mqs-02__group,
.mqs-02__wall:focus-within .mqs-02__group {
  animation-play-state: paused;
}

.mqs-02__card {
  flex: none;
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 15px;
  font-weight: 600;
  color: #cbd5e1;
  background: #1e293b;
  border: 1px solid #334155;
  border-radius: 12px;
  padding: 14px 22px;
  transition: transform .2s,border-color .2s,color .2s;
  cursor: default;
}

.mqs-02__card:hover {
  transform: translateY(-3px);
  border-color: #64748b;
  color: #f8fafc;
}

.mqs-02__dot {
  width: 10px;
  height: 10px;
  border-radius: 3px;
  background: var(--c,#64748b);
}

@media (prefers-reduced-motion: reduce) {
  .mqs-02__group {
    animation: none;
  }

  .mqs-02__viewport {
    overflow-x: auto;
    mask-image: none;
    -webkit-mask-image: none;
  }
}
/* No JavaScript — two rows share one keyframe; the second row only adds animation-direction:reverse and a different --speed token. */
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Infinite Marquee Design from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: B2B Client Logo Wall — Two-Row Counter-Scrolling Marquee
Source: https://codefronts.com/motion/css-infinite-marquee/b2b-client-logo-wall-two-row/

An agency-grade client wall: two logo rows gliding in opposite directions at slightly different speeds, wrapped in badge-style logo cards that lift on hover while the whole wall pauses — the counter-motion reads as 'lots of clients' at a glance without a single line of JavaScript.
## HTML
```html
<section class="mqs-02" aria-label="Client logo wall marquee demo">
  <div class="mqs-02__inner">
    <h3 class="mqs-02__title">Trusted by 200+ product teams</h3>
    <div class="mqs-02__wall">
      <div class="mqs-02__viewport">
        <div class="mqs-02__group" style="--speed:30s">
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#2563eb"></span>Northbeam</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#dc2626"></span>Cartload</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#059669"></span>Fernwork</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#7c3aed"></span>Quill &amp; Co</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#ea580c"></span>Baseline</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#0891b2"></span>Harbourly</span>
        </div>
        <div class="mqs-02__group" style="--speed:30s" aria-hidden="true">
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#2563eb"></span>Northbeam</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#dc2626"></span>Cartload</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#059669"></span>Fernwork</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#7c3aed"></span>Quill &amp; Co</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#ea580c"></span>Baseline</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#0891b2"></span>Harbourly</span>
        </div>
      </div>
      <div class="mqs-02__viewport">
        <div class="mqs-02__group mqs-02__group--rev" style="--speed:38s">
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#be185d"></span>Alpenglow</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#4d7c0f"></span>Statfield</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#b45309"></span>Mooring</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#1d4ed8"></span>Deltaform</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#0f766e"></span>Palisade</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#9333ea"></span>Winward</span>
        </div>
        <div class="mqs-02__group mqs-02__group--rev" style="--speed:38s" aria-hidden="true">
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#be185d"></span>Alpenglow</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#4d7c0f"></span>Statfield</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#b45309"></span>Mooring</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#1d4ed8"></span>Deltaform</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#0f766e"></span>Palisade</span>
          <span class="mqs-02__card"><span class="mqs-02__dot" style="--c:#9333ea"></span>Winward</span>
        </div>
      </div>
    </div>
  </div>
</section>
```
## CSS
```css
.mqs-02,
.mqs-02 *,
.mqs-02 *::before,
.mqs-02 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.mqs-02 {
  min-height: 100vh;
  width: 100%;
  --gap: 20px;
  font-family: 'Segoe UI',system-ui,sans-serif;
  background: #0f172a;
  padding: 64px 24px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.mqs-02__inner {
  width: min(960px,100%);
}

.mqs-02__title {
  font-size: clamp(18px,3vw,24px);
  font-weight: 700;
  color: #e2e8f0;
  text-align: center;
  margin-bottom: 34px;
  letter-spacing: -.01em;
}

.mqs-02__wall {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
}

.mqs-02__viewport {
  display: flex;
  gap: var(--gap);
  overflow: hidden;
  mask-image: linear-gradient(90deg,transparent,#000 10%,#000 90%,transparent);
  -webkit-mask-image: linear-gradient(90deg,transparent,#000 10%,#000 90%,transparent);
}

.mqs-02__group {
  flex: none;
  display: flex;
  gap: var(--gap);
  animation: mqs-02-scroll var(--speed,30s) linear infinite;
  will-change: transform;
}

.mqs-02__group--rev {
  animation-direction: reverse;
}

@keyframes mqs-02-scroll {
  to {
    transform: translateX(calc(-100% - var(--gap)));
  }
}

.mqs-02__wall:hover .mqs-02__group,
.mqs-02__wall:focus-within .mqs-02__group {
  animation-play-state: paused;
}

.mqs-02__card {
  flex: none;
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 15px;
  font-weight: 600;
  color: #cbd5e1;
  background: #1e293b;
  border: 1px solid #334155;
  border-radius: 12px;
  padding: 14px 22px;
  transition: transform .2s,border-color .2s,color .2s;
  cursor: default;
}

.mqs-02__card:hover {
  transform: translateY(-3px);
  border-color: #64748b;
  color: #f8fafc;
}

.mqs-02__dot {
  width: 10px;
  height: 10px;
  border-radius: 3px;
  background: var(--c,#64748b);
}

@media (prefers-reduced-motion: reduce) {
  .mqs-02__group {
    animation: none;
  }

  .mqs-02__viewport {
    overflow-x: auto;
    mask-image: none;
    -webkit-mask-image: none;
  }
}
```

## JavaScript
```js
/* No JavaScript — two rows share one keyframe; the second row only adds animation-direction:reverse and a different --speed token. */
```

How this works

Both rows reuse one loop skeleton (viewport + two aria-hidden-duplicated groups); the second row simply adds animation-direction: reverse and a different --speed. Because speed and direction are per-row custom properties, the counter-scroll costs two declarations, not a second keyframe block.

Each logo is a card (border, radius, subtle shadow) rather than bare text — cards give the row a measurable rhythm and a hover target. On :hover/:focus-within of the wall, every group's animation-play-state flips to paused so the logo under the cursor stops dead — the single most-searched marquee behaviour ('pause on hover'). The hovered card lifts with translateY(-3px); since the group's transform lives on the group and the lift lives on the card, they never fight.

Different speeds matter: rows at 30s and 38s drift out of phase, so the wall never forms an accidental grid-lock pattern where both rows align — a subtle detail that makes the wall feel organic.

Techniques used: per-row --speed / animation-direction tokens on one shared keyframe · pause-on-hover + :focus-within via animation-play-state · card hover lift on a child so it composes with the group transform · counter-phase row speeds · mask-image edge fade · reduced-motion scroll fallback

Make it yours

  • Row direction: add or remove animation-direction:reverse per row — a third row alternating again reads great for 20+ logos.
  • Stagger feel: keep row speeds 20–30% apart (e.g. 30s / 38s) so rows never sync up.
  • Card density: --gap controls both flex gap and the keyframe travel — one token.
  • Make cards links (<a>) — :focus-within already pauses the wall for keyboard users tabbing through them.

Gotchas — read before shipping

  • Pause on hover on the WALL (the shared wrapper), not per row — half-paused walls look broken.
  • Cards need flex:none; otherwise flexbox shrinks them and the row width (and loop math) changes.
  • Don't put the hover lift transform on the group — it overrides the marquee keyframe transform. Child elements compose transforms; same element does not.
  • If rows can receive keyboard focus (linked logos), the mask edge fade will hide the focused card at the edges — the :focus-within pause mitigates this, but test with Tab.

Browser support

ChromeSafariFirefoxEdge
all15.4+allall

Pure baseline: CSS animation, custom properties, :focus-within. mask-image edge fade is baseline 2023; without it edges are simply hard cuts.

Techniques used in this demo

Search CodeFronts

Loading…