Infinite Circular Scroll for React

Vertical list of cards with infinite scroll. Scroll past the last to wrap to the first.

Component

1Card 1
2Card 2
3Card 3
4Card 4
5Card 5
6Card 6
7Card 7
8Card 8
9Card 9
10Card 10
1Card 1
2Card 2
3Card 3
4Card 4
5Card 5
6Card 6
7Card 7
8Card 8
9Card 9
10Card 10
1Card 1
2Card 2
3Card 3
4Card 4
5Card 5
6Card 6
7Card 7
8Card 8
9Card 9
10Card 10

Installation

 

Usage

Import

Add the import.

import { InfiniteCircularScroll } from "@/components/infinite-circular-scroll";

Use

Pass cards as children. Scroll infinitely.

<InfiniteCircularScroll itemHeight={56}>
  {items.map((item) => (
    <Card key={item.id} {...item} />
  ))}
</InfiniteCircularScroll>;

Guidelines

  • Pass cards as children. They are arranged in a vertical list.
  • Scrollbar is hidden. Scroll wraps infinitely.
  • itemHeight must match your card height for correct alignment.

Props

All props are optional unless marked required. Use these to customize every aspect of the component.

PropTypeDefaultDescription
childrenReact.ReactNode-Cards or items to display in the list.
classNamestring-Additional CSS classes for the container.
itemClassNamestring-Additional CSS classes for each item wrapper.
itemHeightnumber64Height of each item in pixels.

Accessibility

  • The scroll container is not made keyboard-focusable; it has no tabindex, so keyboard-only users cannot reliably focus and scroll it, and no visible focus ring is applied.
  • There are no interactive controls (no buttons, inputs, or links) and no ARIA roles or labels, so the looping list is announced only as its plain child content.
  • Any focusable elements passed in as children are duplicated across the three rendered copies, which can produce repeated focus stops and duplicate accessible names for assistive technology.
  • It does not check prefers-reduced-motion; since wrapping relies on native scrolling the motion is user-driven, but smooth-scroll or auto-advance built on top should be guarded with a prefers-reduced-motion media query.

Performance

  • The wrap effect only reassigns scrollTop when crossing the lower and upper thresholds, avoiding per-frame layout work, but those scrollTop writes still trigger synchronous scroll position updates.
  • Nothing animates via CSS transitions or transforms; motion is the browser native scroll, so there is no compositor work, will-change hint, or requestAnimationFrame loop to manage.
  • Children are cloned three times, so a large list triples the DOM node count and the layout and paint cost of the container.
  • The scroll and wheel listeners are registered once per effect run and removed in the cleanup, and overscrollBehavior contain prevents scroll chaining to ancestors.
  • The scroll handler runs on every scroll event without throttling, so it fires at the browser scroll rate, though its work is limited to a couple of comparisons and an occasional scrollTop assignment.

Examples

Basic

Numbered cards 1–10. Scroll infinitely.

1Card 1
2Card 2
3Card 3
4Card 4
5Card 5
6Card 6
7Card 7
8Card 8
9Card 9
10Card 10
1Card 1
2Card 2
3Card 3
4Card 4
5Card 5
6Card 6
7Card 7
8Card 8
9Card 9
10Card 10
1Card 1
2Card 2
3Card 3
4Card 4
5Card 5
6Card 6
7Card 7
8Card 8
9Card 9
10Card 10
import { InfiniteCircularScroll } from "@/components/infinite-circular-scroll";

<InfiniteCircularScroll
  itemHeight={56}
  className="rounded-lg border border-border"
>
  {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((i) => (
    <div
      key={i}
      className="flex items-center gap-3 rounded-lg border bg-muted/50 px-4 py-2"
    >
      <span className="font-mono font-bold">{i}</span>
      <span className="text-muted-foreground text-sm">
        Card {i}
      </span>
    </div>
  ))}
</InfiniteCircularScroll>;

Last updated on Jun 19

Made with ❤️ by Pulkit &

© 2026 Pulkit. All rights reserved

Last updated: