Marquee

A flexible scrolling layout for showcasing repeating content like logos, announcements, or testimonials.

Knockout
React
Angular
Vue
Svelte
Solid
Astro
Marko
Wiz
Qwik

Knockout
React
Angular
Vue
Svelte
Solid
Astro
Marko
Wiz
Qwik

1. Copy the component file

Create a new file called marquee.jsx in your reusable components folder (for example /src/components/) and paste the following code into it.

marquee.jsx

import { memo } from "react";
import styles from "./marquee.module.css";

const Marquee = (props) => {
  const {
    children,
    axis = "horizontal",
    pauseOnHover = true,
    reverse = false,
    duration = 30,
    repeat = 5,
    mask = true,
    className,
    ...restProps
  } = props;

  const _repeat = Math.max(1, repeat);
  const _duration = Math.max(1, duration);

  return (
    <div
      {...restProps}
      className={[
        className,
        styles["marquee"],
        styles[`axis-${axis}`],
        reverse ? styles["reverse"] : "",
        pauseOnHover ? styles["pause-on-hover"] : "",
        mask ? styles["mask"] : "",
      ].join(" ")}
    >
      <div
        className={styles["wrapper"]}
        style={{
          "--animation-duration": `${_duration}s`
        }}
      >
        {Array.from({
          length: _repeat,
        }).map((_, index) => (
          <div
            key={`marquee-block-${index}`}
            aria-hidden={index !== 0}
            className={styles["batch"]}
          >
            {children}
          </div>
        ))}
      </div>
    </div>
  );
};

export default memo(Marquee);

2. Copy the CSS module file

In the same folder, create a file called marquee.module.css and paste the following CSS.

marquee.module.css

.marquee {
  --gap: 16px;
  --mask-breakpoint: 20%;

  overflow: hidden;

  >.wrapper {
    display: flex;
    gap: var(--gap);
    width: max-content;

    >.batch {
      display: flex;
      gap: var(--gap);
      animation-duration: var(--animation-duration);
      animation-timing-function: linear;
      animation-iteration-count: infinite;
    }
  }

  &.pause-on-hover:hover {
    >.wrapper {
      >.batch {
        animation-play-state: paused;
      }
    }
  }

  &.reverse {
    >.wrapper {
      >.batch {
        animation-direction: reverse;
      }
    }
  }

  &.axis-horizontal {
    &.mask {
      mask: linear-gradient(
        to right, 
        transparent, 
        white var(--mask-breakpoint), 
        white calc(100% - var(--mask-breakpoint)), 
        transparent
      );
    }

    >.wrapper {
      flex-direction: row;

      >.batch {
        flex-direction: row;
        animation-name: marquee-list-horizontal-keyframes;
      }
    }
  }

  &.axis-vertical {
    &.mask {
      mask: linear-gradient(
        to bottom, 
        transparent, 
        white var(--mask-breakpoint), 
        white calc(100% - var(--mask-breakpoint)), 
        transparent
      );
    }

    >.wrapper {
      flex-direction: column;

      >.batch {
        flex-direction: column;
        animation-name: marquee-list-vertical-keyframes;
      }
    }
  }
}

@keyframes marquee-list-horizontal-keyframes {
  from {
    transform: translateX(0);
  }

  to {
    transform: translateX(calc(-100% - var(--gap)));
  }
}

@keyframes marquee-list-vertical-keyframes {
  from {
    transform: translateY(0);
  }

  to {
    transform: translateY(calc(-100% - var(--gap)));
  }
}

3. Use the component

Now you can import and use the component anywhere in your project.

marquee-preview.jsx

import Marquee from "@/components/essentials/marquee/marquee";

const MarqueePreview = () => {
  const javaScriptLibAndFrameworks = ["Knockout", "React", "Angular", "Vue", "Svelte", "Solid", "Astro", "Marko", "Wiz", "Qwik"];
  return (
    <div style={{
      width: "100%",
      minWidth: "0",
      padding: "32px",
    }}>
      <Marquee>
        {javaScriptLibAndFrameworks.map(e => (
          <Card key={e} name={e} />
        ))}
      </Marquee>
      <br />
      <Marquee reverse>
        {javaScriptLibAndFrameworks.map(e => (
          <Card key={e} name={e} />
        ))}
      </Marquee>
    </div>
  )
};

const Card = (props) => {
  const {
    name
  } = props;

  return (
    <div
      style={{
        padding: "16px",
        background: "var(--layer-tertiary)",
        borderRadius: "4px",
      }}
    >
      {name}
    </div>
  )
};

export default MarqueePreview;

Vertical

Knockout
React
Angular
Vue
Svelte
Solid
Astro
Marko
Wiz
Qwik

Knockout
React
Angular
Vue
Svelte
Solid
Astro
Marko
Wiz
Qwik

marquee

import Marquee from "@/components/essentials/marquee/marquee";

/**
 * When vertical axis is used, add max-height / height to container of Marquee.
 */
const MarqueeAxisPreview = () => {
  const javaScriptLibAndFrameworks = ["Knockout", "React", "Angular", "Vue", "Svelte", "Solid", "Astro", "Marko", "Wiz", "Qwik"];
  return (
    <div style={{
      display: "flex",
      justifyContent: "center",
      width: "100%",
      minWidth: "0",
      maxHeight: "320px", // <- This is important when vertical axis is used.
      padding: "32px",
      gap: "8px",
    }}>
      <Marquee
        axis="vertical"
      >
        {javaScriptLibAndFrameworks.map(e => (
          <Card key={e} name={e} />
        ))}
      </Marquee>
      <br />
      <Marquee 
        axis="vertical"
        reverse
      >
        {javaScriptLibAndFrameworks.map(e => (
          <Card key={e} name={e} />
        ))}
      </Marquee>
    </div>
  )
};

const Card = (props) => {
  const {
    name
  } = props;

  return (
    <div
      style={{
        padding: "16px",
        background: "var(--layer-tertiary)",
        borderRadius: "4px",
      }}
    >
      {name}
    </div>
  )
};

export default MarqueeAxisPreview;

Duration

Knockout
React
Angular
Vue
Svelte
Solid
Astro
Marko
Wiz
Qwik

marquee

import Marquee from "@/components/essentials/marquee/marquee";

const MarqueePreview = () => {
  const javaScriptLibAndFrameworks = ["Knockout", "React", "Angular", "Vue", "Svelte", "Solid", "Astro", "Marko", "Wiz", "Qwik"];
  return (
    <div style={{
      width: "100%",
      minWidth: "0",
      padding: "32px",
    }}>
      <Marquee duration={5}>
        {javaScriptLibAndFrameworks.map(e => (
          <Card key={e} name={e} />
        ))}
      </Marquee>
    </div>
  )
};

const Card = (props) => {
  const {
    name
  } = props;

  return (
    <div
      style={{
        padding: "16px",
        background: "var(--layer-tertiary)",
        borderRadius: "4px",
      }}
    >
      {name}
    </div>
  )
};

export default MarqueePreview;

Props

Configure the component with the following props:

PropTypeRequiredDefaultDescription
childrenReact.ReactNodeYesItems to be displayed inside the marquee. These elements will scroll continuously.
axis"horizontal" | "vertical"No"horizontal"Controls the scrolling direction of the marquee.
pauseOnHoverbooleanNotruePauses the marquee animation when the user hovers over it.
reversebooleanNofalseReverses the scrolling direction of the marquee animation.
durationnumberNo30Duration of one animation cycle in seconds. Minimum value is 1.
repeatnumberNo5Number of times the marquee content is repeated to maintain continuous scrolling. Increase this if the marquee items are small.
maskbooleanNotrueApplies a fade mask at the beginning and end of the marquee.
classNamestringNoAdditional CSS class names applied to the marquee container.
styleReact.CSSPropertiesNoInline styles applied to the marquee container.