Pure TailwindMIT licensed

Tailwind Dual Opposite-Direction Marquee Slider

Two image tracks gliding in opposite directions — the top row drifts left while the bottom drifts right. One keyframe, two @theme animation tokens, and the second one differs from the first by a single word: reverse.

Live Demo
Plain CSSTailwind v4
Try it

Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time

Untitled UI React · Affiliate link

Get Untitled UI Pro

The code

<section class="tcar-15 w-full min-h-svh grid place-items-center bg-tcar15-bg font-tcar15-sans py-10" aria-label="Moodboard marquee">
  <div class="group tcar15-band w-full flex flex-col gap-4 overflow-hidden [mask-image:linear-gradient(90deg,transparent,#000_8%,#000_92%,transparent)]">
    <ul class="list-none flex gap-4 w-max m-0 p-0 animate-tcar15-run group-hover:[animation-play-state:paused] group-focus-within:[animation-play-state:paused] motion-reduce:animate-none motion-reduce:flex-wrap motion-reduce:justify-center motion-reduce:w-auto">
      <li class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-1/300/220" width="300" height="220" decoding="async" alt="Studio still life" class="size-full object-cover block"></li>
      <li class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-2/300/220" width="300" height="220" decoding="async" alt="Architectural detail" class="size-full object-cover block"></li>
      <li class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-3/300/220" width="300" height="220" decoding="async" alt="Textile close-up" class="size-full object-cover block"></li>
      <li class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-4/300/220" width="300" height="220" decoding="async" alt="Product on plinth" class="size-full object-cover block"></li>
      <li aria-hidden="true" class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-1/300/220" width="300" height="220" loading="lazy" decoding="async" alt="" class="size-full object-cover block"></li>
      <li aria-hidden="true" class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-2/300/220" width="300" height="220" loading="lazy" decoding="async" alt="" class="size-full object-cover block"></li>
      <li aria-hidden="true" class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-3/300/220" width="300" height="220" loading="lazy" decoding="async" alt="" class="size-full object-cover block"></li>
      <li aria-hidden="true" class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-4/300/220" width="300" height="220" loading="lazy" decoding="async" alt="" class="size-full object-cover block"></li>
    </ul>
    <ul class="list-none flex gap-4 w-max m-0 p-0 animate-tcar15-back group-hover:[animation-play-state:paused] group-focus-within:[animation-play-state:paused] motion-reduce:animate-none motion-reduce:flex-wrap motion-reduce:justify-center motion-reduce:w-auto">
      <li class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-5/300/220" width="300" height="220" decoding="async" alt="Gradient backdrop" class="size-full object-cover block"></li>
      <li class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-6/300/220" width="300" height="220" decoding="async" alt="Ceramic vessels" class="size-full object-cover block"></li>
      <li class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-7/300/220" width="300" height="220" decoding="async" alt="Folded paper" class="size-full object-cover block"></li>
      <li class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-8/300/220" width="300" height="220" decoding="async" alt="Woven basket" class="size-full object-cover block"></li>
      <li aria-hidden="true" class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-5/300/220" width="300" height="220" loading="lazy" decoding="async" alt="" class="size-full object-cover block"></li>
      <li aria-hidden="true" class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-6/300/220" width="300" height="220" loading="lazy" decoding="async" alt="" class="size-full object-cover block"></li>
      <li aria-hidden="true" class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-7/300/220" width="300" height="220" loading="lazy" decoding="async" alt="" class="size-full object-cover block"></li>
      <li aria-hidden="true" class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-8/300/220" width="300" height="220" loading="lazy" decoding="async" alt="" class="size-full object-cover block"></li>
    </ul>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-tcar15-bg: #14121a;
  --color-tcar15-bg: oklch(0.19 0.015 300);
  --color-tcar15-tile: #221f2b;
  --color-tcar15-tile: oklch(0.25 0.018 300);
  --color-tcar15-acc: #f43f5e;
  --color-tcar15-acc: oklch(0.65 0.21 15);
  --font-tcar15-sans: 'Space Grotesk', system-ui, sans-serif;
  --animate-tcar15-run: tcar15-run 30s linear infinite;
  --animate-tcar15-back: tcar15-run 30s linear infinite reverse;
}

@keyframes tcar15-run {
  from {
    transform: translateX(0);
  }

  to {
    transform: translateX(-50%);
  }
}

.tcar-15 {
  width: 100%;
  min-height: 100svh;
}

.tcar-15 ::selection {
  background: var(--color-tcar15-acc);
  color: #fff;
}
/* Safari < 15.4 mask prefix. */

.tcar15-band {
  -webkit-mask: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>

Compiles in the browser — perfect for a CodePen or a quick test, not for production.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@import "tailwindcss";

@theme {
  --color-tcar15-bg: #14121a;
  --color-tcar15-bg: oklch(0.19 0.015 300);
  --color-tcar15-tile: #221f2b;
  --color-tcar15-tile: oklch(0.25 0.018 300);
  --color-tcar15-acc: #f43f5e;
  --color-tcar15-acc: oklch(0.65 0.21 15);
  --font-tcar15-sans: 'Space Grotesk', system-ui, sans-serif;
  --animate-tcar15-run: tcar15-run 30s linear infinite;
  --animate-tcar15-back: tcar15-run 30s linear infinite reverse;
}

@keyframes tcar15-run {
  from {
    transform: translateX(0);
  }

  to {
    transform: translateX(-50%);
  }
}

.tcar-15 {
  width: 100%;
  min-height: 100svh;
}

.tcar-15 ::selection {
  background: var(--color-tcar15-acc);
  color: #fff;
}
/* Safari < 15.4 mask prefix. */

.tcar15-band {
  -webkit-mask: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
3Fonts used by this demooptional — skip if you have your own type stack
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap">

What changed in the port

Which parts became utilities, which stayed CSS, and why.

  • Two @theme entries pointing at one @keyframes is the tidiest thing in this port. The --animate-* namespace makes "the same motion, backwards" a named token rather than an extra declaration bolted onto a class.
  • The tracks are w-max, not a percentage. A marquee's width is the sum of its content, and any attempt to express that as a utility scale is a mistake.
  • UI change from the CSS original: the reduced-motion fallback now centres and wraps into a genuine mosaic rather than leaving a w-max row overflowing its masked band. The original's static state was clipped on the right for anyone with motion reduced.
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 demo 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: Tailwind Dual Opposite-Direction Marquee Slider
Source: https://codefronts.com/snippets/tailwind-css-image-carousel/tailwind-dual-opposite-direction-marquee-slider/

Two image tracks gliding in opposite directions — the top row drifts left while the bottom drifts right. One keyframe, two @theme animation tokens, and the second one differs from the first by a single word: reverse.
## HTML
```html
<section class="tcar-15 w-full min-h-svh grid place-items-center bg-tcar15-bg font-tcar15-sans py-10" aria-label="Moodboard marquee">
  <div class="group tcar15-band w-full flex flex-col gap-4 overflow-hidden [mask-image:linear-gradient(90deg,transparent,#000_8%,#000_92%,transparent)]">
    <ul class="list-none flex gap-4 w-max m-0 p-0 animate-tcar15-run group-hover:[animation-play-state:paused] group-focus-within:[animation-play-state:paused] motion-reduce:animate-none motion-reduce:flex-wrap motion-reduce:justify-center motion-reduce:w-auto">
      <li class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-1/300/220" width="300" height="220" decoding="async" alt="Studio still life" class="size-full object-cover block"></li>
      <li class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-2/300/220" width="300" height="220" decoding="async" alt="Architectural detail" class="size-full object-cover block"></li>
      <li class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-3/300/220" width="300" height="220" decoding="async" alt="Textile close-up" class="size-full object-cover block"></li>
      <li class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-4/300/220" width="300" height="220" decoding="async" alt="Product on plinth" class="size-full object-cover block"></li>
      <li aria-hidden="true" class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-1/300/220" width="300" height="220" loading="lazy" decoding="async" alt="" class="size-full object-cover block"></li>
      <li aria-hidden="true" class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-2/300/220" width="300" height="220" loading="lazy" decoding="async" alt="" class="size-full object-cover block"></li>
      <li aria-hidden="true" class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-3/300/220" width="300" height="220" loading="lazy" decoding="async" alt="" class="size-full object-cover block"></li>
      <li aria-hidden="true" class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-4/300/220" width="300" height="220" loading="lazy" decoding="async" alt="" class="size-full object-cover block"></li>
    </ul>
    <ul class="list-none flex gap-4 w-max m-0 p-0 animate-tcar15-back group-hover:[animation-play-state:paused] group-focus-within:[animation-play-state:paused] motion-reduce:animate-none motion-reduce:flex-wrap motion-reduce:justify-center motion-reduce:w-auto">
      <li class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-5/300/220" width="300" height="220" decoding="async" alt="Gradient backdrop" class="size-full object-cover block"></li>
      <li class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-6/300/220" width="300" height="220" decoding="async" alt="Ceramic vessels" class="size-full object-cover block"></li>
      <li class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-7/300/220" width="300" height="220" decoding="async" alt="Folded paper" class="size-full object-cover block"></li>
      <li class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-8/300/220" width="300" height="220" decoding="async" alt="Woven basket" class="size-full object-cover block"></li>
      <li aria-hidden="true" class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-5/300/220" width="300" height="220" loading="lazy" decoding="async" alt="" class="size-full object-cover block"></li>
      <li aria-hidden="true" class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-6/300/220" width="300" height="220" loading="lazy" decoding="async" alt="" class="size-full object-cover block"></li>
      <li aria-hidden="true" class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-7/300/220" width="300" height="220" loading="lazy" decoding="async" alt="" class="size-full object-cover block"></li>
      <li aria-hidden="true" class="flex-none w-[210px] aspect-[15/11] rounded-xl overflow-hidden bg-tcar15-tile"><img src="https://picsum.photos/seed/du-8/300/220" width="300" height="220" loading="lazy" decoding="async" alt="" class="size-full object-cover block"></li>
    </ul>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tcar15-bg: #14121a;
  --color-tcar15-bg: oklch(0.19 0.015 300);
  --color-tcar15-tile: #221f2b;
  --color-tcar15-tile: oklch(0.25 0.018 300);
  --color-tcar15-acc: #f43f5e;
  --color-tcar15-acc: oklch(0.65 0.21 15);
  --font-tcar15-sans: 'Space Grotesk', system-ui, sans-serif;
  --animate-tcar15-run: tcar15-run 30s linear infinite;
  --animate-tcar15-back: tcar15-run 30s linear infinite reverse;
}

@keyframes tcar15-run {
  from {
    transform: translateX(0);
  }

  to {
    transform: translateX(-50%);
  }
}

.tcar-15 {
  width: 100%;
  min-height: 100svh;
}

.tcar-15 ::selection {
  background: var(--color-tcar15-acc);
  color: #fff;
}
/* Safari < 15.4 mask prefix. */

.tcar15-band {
  -webkit-mask: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
```

How this works

Direction is a token, not a utility. Tailwind has no animate-reverse, and should not: reversal is part of the animation's identity. A second @theme entry pointing at the same keyframe with reverse appended names the behaviour properly and mints its own utility.

Both tracks need the same two-copy contract. Each holds its images twice and the keyframe ends at exactly -50%; the duplicates carry aria-hidden="true".

One group pauses both. The band is the group, so hovering anywhere stops the whole composition rather than half of it.

Make it yours

  • Independent speeds: give each token its own duration.
  • Swap directions: exchange the two utilities on the tracks.
  • Third row: a third token, a third track.
  • Edge feather: the mask stops on the band.

Gotchas — read before shipping

  • Two identical halves and a -50% endpoint, per track. This is the one rule the pattern cannot bend.
  • aria-hidden="true" on every duplicated tile, or the moodboard is announced twice.
  • Pausing must cover both tracks. A band where one row freezes and the other keeps moving reads as a bug.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

CSS animation and animation-direction are universal; mask-image needs Safari 15.4+ (shim included). The floor quoted is Tailwind v4's generated sheet.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Arbitraryneeds bracket syntax

Plain CSSno Tailwind surface

Search CodeFronts

Loading…