npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tavosud/sky-marquee

v1.1.4

Published

Ultra-lightweight infinite marquee component for React with native CSS animations

Downloads

519

Readme

@tavosud/sky-marquee

Ultra-lightweight infinite marquee component for React with native CSS animations. Works with images, logos, text, or any React element.

npm version license demo ko-fi

Features

  • Zero runtime dependencies — no external libraries
  • Dual ESM + CJS — works in Vite, Next.js, CRA, Node.js, and Jest
  • Native CSS animations — GPU-accelerated with will-change: transform
  • 4 directions — left, right, up, down
  • Responsive props — per-breakpoint values for speed, gap, direction, and more
  • Accessible — ARIA roles, keyboard pause/resume, prefers-reduced-motion support
  • Fully typed — complete TypeScript definitions included
  • Touch support — pauses on touch, resumes on release
  • Memoized variantMemoizedMarquee to prevent unnecessary re-renders

Installation

npm install @tavosud/sky-marquee

Peer dependencies: React >= 18.0.0 · React DOM >= 18.0.0

CSS import required. The stylesheet is bundled in dist/. If your bundler does not import CSS automatically, add:

import '@tavosud/sky-marquee/dist/marquee.css';

Quick Start

import { Marquee } from '@tavosud/sky-marquee';

function App() {
  return (
    <Marquee speed={20} gap="40px">
      <img src="/logo1.svg" alt="Logo 1" width={120} />
      <img src="/logo2.svg" alt="Logo 2" width={120} />
      <img src="/logo3.svg" alt="Logo 3" width={120} />
    </Marquee>
  );
}

Props

| Prop | Type | Responsive | Default | Description | |------|------|:----------:|---------|-------------| | children | ReactNode | — | required | Elements to display | | speed | number | Yes | 20 | Animation duration in seconds (lower = faster) | | direction | MarqueeDirection | Yes | 'left' | Scroll direction | | gap | string | Yes | '40px' | Spacing between elements (any CSS unit) | | fadeEdges | boolean | Yes | false | Fade gradient on leading/trailing edges | | verticalHeight | string | Yes | '200px' | Container height for vertical directions | | pauseOnHover | boolean | — | true | Pause animation on mouse hover | | paused | boolean | — | false | Programmatic pause control | | easing | MarqueeEasing | — | 'linear' | CSS animation timing function | | autoFill | boolean | — | false | Auto-repeat children until they fill the container | | className | string | — | undefined | Extra CSS class on the container element | | style | CSSProperties | — | undefined | Inline styles merged onto the container element | | onCycle | () => void | — | undefined | Callback fired on each full animation cycle |

Types

type MarqueeDirection = 'left' | 'right' | 'up' | 'down';
type MarqueeEasing    = 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out';

Responsive Props

Props marked Responsive accept either a plain value or a breakpoint object:

<Marquee
  speed={{ base: 10, sm: 15, md: 20, lg: 30 }}
  gap={{ base: '20px', md: '40px', lg: '60px' }}
  direction={{ base: 'left', md: 'right' }}
  fadeEdges={{ base: false, lg: true }}
  verticalHeight={{ base: '150px', md: '200px', lg: '250px' }}
>
  <span>Responsive item</span>
</Marquee>

| Breakpoint | Min width | Targets | |------------|-----------|---------| | base | 0 px | All screens (default) | | sm | 640 px | Large phones / small tablets | | md | 768 px | Tablets | | lg | 1024 px | Desktops |

Values cascade upward — a sm value applies on md and lg unless overridden.

Examples

Logo / image strip

<Marquee speed={15} gap={{ base: '30px', lg: '60px' }} fadeEdges>
  <img src="/brand-a.svg" alt="Brand A" width={100} />
  <img src="/brand-b.svg" alt="Brand B" width={100} />
  <img src="/brand-c.svg" alt="Brand C" width={100} />
</Marquee>

Auto-fill container

When you have few items that don't fill the viewport, enable autoFill to repeat them automatically:

<Marquee autoFill speed={20} gap="40px">
  <img src="/logo.svg" alt="Logo" width={100} />
  <img src="/logo2.svg" alt="Logo 2" width={100} />
</Marquee>

Custom className and style

<Marquee className="my-strip" style={{ background: '#0f0f0f', padding: '16px 0' }}>
  <img src="/logo.svg" alt="Logo" width={100} />
</Marquee>

Reverse direction

<Marquee direction="right" speed={25}>
  <img src="/logo.png" alt="Logo" width={80} />
</Marquee>

Vertical ticker

<Marquee direction="up" verticalHeight="300px" gap="16px">
  <div className="card">News item 1</div>
  <div className="card">News item 2</div>
  <div className="card">News item 3</div>
</Marquee>

Programmatic pause

import { useState } from 'react';

function App() {
  const [isPaused, setIsPaused] = useState(false);

  return (
    <>
      <button onClick={() => setIsPaused(p => !p)}>
        {isPaused ? 'Play' : 'Pause'}
      </button>
      <Marquee paused={isPaused}>
        <img src="/logo.svg" alt="Logo" width={100} />
      </Marquee>
    </>
  );
}

Cycle callback

<Marquee onCycle={() => console.log('loop!')}>
  <span>Item</span>
</Marquee>

Memoized version

Use MemoizedMarquee when the parent re-renders frequently:

import { MemoizedMarquee } from '@tavosud/sky-marquee';

const LogoStrip = () => (
  <MemoizedMarquee speed={20} gap="40px">
    <img src="/logo.svg" alt="Logo" width={100} />
  </MemoizedMarquee>
);

CSS Customization

Override CSS custom properties on the container element:

.my-marquee {
  --marquee-fade-size: 80px;   /* width/height of edge fade */
  --marquee-fade-color: #fff;  /* color used in the gradient */
  --marquee-height: 200px;     /* vertical mode container height */
}

| Property | Default | Description | |----------|---------|-------------| | --marquee-fade-size | 50px | Width (horizontal) or height (vertical) of the fade gradient | | --marquee-fade-color | white | Fade gradient color — match your background | | --marquee-height | 200px | Container height when direction is up or down |

Accessibility

| Feature | Behavior | |---------|----------| | role="marquee" | Semantic landmark on the container | | aria-live="off" | Screen readers won't announce the scrolling content | | aria-hidden="true" | Applied to cloned (duplicate) children | | Keyboard | Tab to focus container, Space / Enter to toggle pause | | Touch | Pauses on touchstart, resumes on touchend | | prefers-reduced-motion | Animation is disabled entirely |

How It Works

  1. Children are rendered once as the visible track.
  2. The same children are cloned with aria-hidden="true" to form a seamless repeat.
  3. A CSS animation translates the inner container from 0% to -50% (or equivalent for vertical).
  4. When the animation reaches the end, it loops back — creating an infinite scroll effect.

Breakpoint detection relies on window.innerWidth and a resize listener — no ResizeObserver or third-party libraries needed.

Module Formats

| File | Format | Use case | |------|--------|----------| | dist/index.js | ESM | Vite, Next.js, modern bundlers | | dist/index.cjs | CJS | Node.js, Jest, older toolchains | | dist/index.d.ts | Types | TypeScript projects |

Browser Support

| Browser | Version | |---------|---------| | Chrome | 36+ | | Firefox | 16+ | | Safari | 9+ | | Edge | 12+ |

License

MIT © tavosud