Marquee

A scrolling text component that animates horizontally.

The Marquee component displays a continuous horizontal scrolling animation. It supports customizable speed, direction, and pause on hover functionality. Built from scratch using React and CSS animations. No dependencies on any UI library.

Code

TypeScript: Copy this code into components/ui/marquee.tsx:

tsx
"use client"

import * as React from "react"
import { cn } from "@/lib/utils"

interface MarqueeProps extends React.HTMLAttributes<HTMLDivElement> {
  children: React.ReactNode
  direction?: "left" | "right"
  speed?: number
  pauseOnHover?: boolean
  className?: string
}

const Marquee = React.forwardRef<HTMLDivElement, MarqueeProps>(
  ({ children, direction = "left", speed = 50, pauseOnHover = false, className, ...props }, ref) => {
    const [isPaused, setIsPaused] = React.useState(false)
    const childrenArray = React.Children.toArray(children)
    
    const duration = `${speed}s`
    const animationName = direction === "left" ? "marquee" : "marquee-right"

    return (
      <div
        ref={ref}
        className={cn("overflow-hidden whitespace-nowrap border-t-2 border-b-2 border-foreground py-4", className)}
        onMouseEnter={() => pauseOnHover && setIsPaused(true)}
        onMouseLeave={() => pauseOnHover && setIsPaused(false)}
        {...props}
      >
        <div
          className="flex"
          style={{
            animationName: isPaused ? "none" : animationName,
            animationDuration: isPaused ? "0s" : duration,
            animationTimingFunction: "linear",
            animationIterationCount: "infinite",
          }}
        >
          {React.Children.map(childrenArray, (child, index) => (
            <div key={`first-${index}`} className="flex-shrink-0 px-4">
              {child}
            </div>
          ))}
          {React.Children.map(childrenArray, (child, index) => (
            <div key={`second-${index}`} className="flex-shrink-0 px-4">
              {child}
            </div>
          ))}
        </div>
      </div>
    )
  }
)
Marquee.displayName = "Marquee"

interface MarqueeItemProps extends React.HTMLAttributes<HTMLDivElement> {}

const MarqueeItem = React.forwardRef<HTMLDivElement, MarqueeItemProps>(
  ({ className, ...props }, ref) => (
    <div
      ref={ref}
      className={cn("flex-shrink-0", className)}
      {...props}
    />
  )
)
MarqueeItem.displayName = "MarqueeItem"

export { Marquee, MarqueeItem }

JavaScript: Copy this code into components/ui/marquee.jsx:

jsx
"use client"

import * as React from "react"
import { cn } from "@/lib/utils"

const Marquee = React.forwardRef(
  ({ children, direction = "left", speed = 50, pauseOnHover = false, className, ...props }, ref) => {
    const [isPaused, setIsPaused] = React.useState(false)
    const childrenArray = React.Children.toArray(children)
    
    const duration = `${speed}s`
    const animationName = direction === "left" ? "marquee" : "marquee-right"

    return (
      <div
        ref={ref}
        className={cn("overflow-hidden whitespace-nowrap border-t-2 border-b-2 border-foreground py-4", className)}
        onMouseEnter={() => pauseOnHover && setIsPaused(true)}
        onMouseLeave={() => pauseOnHover && setIsPaused(false)}
        {...props}
      >
        <div
          className="flex"
          style={{
            animationName: isPaused ? "none" : animationName,
            animationDuration: isPaused ? "0s" : duration,
            animationTimingFunction: "linear",
            animationIterationCount: "infinite",
          }}
        >
          {React.Children.map(childrenArray, (child, index) => (
            <div key={`first-${index}`} className="flex-shrink-0 px-4">
              {child}
            </div>
          ))}
          {React.Children.map(childrenArray, (child, index) => (
            <div key={`second-${index}`} className="flex-shrink-0 px-4">
              {child}
            </div>
          ))}
        </div>
      </div>
    )
  }
)
Marquee.displayName = "Marquee"

const MarqueeItem = React.forwardRef(
  ({ className, ...props }, ref) => (
    <div
      ref={ref}
      className={cn("flex-shrink-0", className)}
      {...props}
    />
  )
)
MarqueeItem.displayName = "MarqueeItem"

export { Marquee, MarqueeItem }

Also add this CSS animation to your globals.css:

css
@keyframes marquee {
  0% {
    transform: translateX(0%);
  }
  100% {
    transform: translateX(-50%);
  }
}

@keyframes marquee-right {
  0% {
    transform: translateX(-50%);
  }
  100% {
    transform: translateX(0%);
  }
}

Usage

TypeScript:

tsx
import { Marquee } from "@/components/ui/marquee"

function MyComponent() {
  return (
    <Marquee>
      <span>Item 1</span>
      <span>Item 2</span>
      <span>Item 3</span>
      <span>Item 4</span>
      <span>Item 5</span>
    </Marquee>
  )
}

JavaScript:

jsx
import { Marquee } from "@/components/ui/marquee"

function MyComponent() {
  return (
    <Marquee>
      <span>Item 1</span>
      <span>Item 2</span>
      <span>Item 3</span>
      <span>Item 4</span>
      <span>Item 5</span>
    </Marquee>
  )
}

Make sure you also have the lib/utils.ts file with the cn helper function.

Examples

Default

Item 1
Item 2
Item 3
Item 4
Item 5
Item 1
Item 2
Item 3
Item 4
Item 5

Right Direction

Item 1
Item 2
Item 3
Item 4
Item 5
Item 1
Item 2
Item 3
Item 4
Item 5

Faster Speed

Item 1
Item 2
Item 3
Item 4
Item 5
Item 1
Item 2
Item 3
Item 4
Item 5

Pause on Hover

Item 1
Item 2
Item 3
Item 4
Item 5
Item 1
Item 2
Item 3
Item 4
Item 5