DEV Community

Cover image for ReelKit: A Virtualized TikTok-Style Slider Engine
Konstantin Kai
Konstantin Kai

Posted on

ReelKit: A Virtualized TikTok-Style Slider Engine

You're building a vertical feed — TikTok-style swipe, full-screen slides, thousands of items. You reach for a carousel library and hit the wall: it renders all slides to the DOM, chokes on touch gestures, and bundles half the internet.

ReelKit renders 3 DOM nodes at any time — previous, current, next — whether you have 4 slides or 40,000. Zero dependencies in core. Touch-first with momentum and snap. ~3.7 kB gzipped.

Try it live on StackBlitz — no setup needed.

Quick start

npm install @reelkit/react
Enter fullscreen mode Exit fullscreen mode
import { useState } from 'react';
import { Reel, ReelIndicator } from '@reelkit/react';

const slides = [
  { title: 'Discover', color: '#6366f1' },
  { title: 'Trending', color: '#8b5cf6' },
  { title: 'Following', color: '#ec4899' },
  { title: 'For You', color: '#14b8a6' },
];

export default function App() {
  const [index, setIndex] = useState(0);

  return (
    <Reel
      count={slides.length}
      style={{ width: '100%', height: '100dvh' }}
      direction="vertical"
      enableWheel
      useNavKeys
      afterChange={setIndex}
      itemBuilder={(i, _inRange, size) => (
        <div
          style={{
            width: size[0],
            height: size[1],
            background: slides[i].color,
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            color: 'white',
            fontSize: '2rem',
          }}
        >
          {slides[i].title}
        </div>
      )}
    >
      <ReelIndicator count={slides.length} active={index} />
    </Reel>
  );
}
Enter fullscreen mode Exit fullscreen mode

Swipe, keyboard arrows, mouse wheel — all work out of the box. The size prop is optional: omit it and ReelKit auto-measures via ResizeObserver.

Programmatic navigation

const apiRef = useRef<ReelApi>(null);

<Reel count={100} apiRef={apiRef} itemBuilder={(i) => <Slide index={i} />} />

<button onClick={() => apiRef.current?.prev()}>Prev</button>
<button onClick={() => apiRef.current?.next()}>Next</button>
<button onClick={() => apiRef.current?.goTo(50)}>Jump to 50</button>
Enter fullscreen mode Exit fullscreen mode

Navigation methods return promises — await apiRef.current!.next() resolves when the animation completes, so you can chain transitions sequentially.

How it works

Virtualization

Only render what's visible. ReelKit computes a visible range from the current index:

Current index: 50, count: 10,000
Visible: [49, 50, 51]  ← 3 DOM nodes, always
Enter fullscreen mode Exit fullscreen mode

With loop mode, it wraps at boundaries:

Current index: 0, loop: true
Visible: [9999, 0, 1]  ← seamless wrap
Enter fullscreen mode Exit fullscreen mode

When you call goTo(5000) from index 0, it doesn't animate through 5,000 slides. It temporarily swaps the adjacent slide with the target, animates a single step, and resolves. One smooth transition — the virtualization handles the rest.

Signals, not React state

ReelKit implements its own reactive system:

  • Signal — writable observable value
  • ComputedSignal — lazy derived value (zero cost when unobserved)
  • batch() — groups multiple updates into a single notification pass

The React <Reel> component subscribes to these signals in effects and uses flushSync() on each animation frame to apply transforms synchronously — bypassing React's default batching. Your React tree doesn't re-render during swipes — transforms update at 60fps without touching component state.

When an animation completes, the index and transform value update in a single batch() call — observers never see an intermediate state. Navigation methods (next(), goTo()) return promises that resolve on completion, so you can chain transitions or await before taking the next action.

Touch-first gestures

The gesture controller detects the dominant axis from the initial touch vector and locks to it. It tracks per-frame delta, cumulative distance, and velocity. A fast swipe (> 1400 px/s) or drag past the threshold triggers a slide change with snap-back animation.

Packages

Package What it does Size (gzip)
@reelkit/core Framework-agnostic engine 3.7 kB
@reelkit/react React components + hooks 2.6 kB
@reelkit/react-reel-player Full-screen video reel player 3.8 kB
@reelkit/react-lightbox Image & video gallery lightbox 3.4 kB

@reelkit/core is the engine — all slider logic, gesture detection, keyboard/wheel controllers, and the signal system. Zero dependencies. Framework-agnostic. Vue bindings are in progress.

Everything in core is factory functions, not classes — createSliderController, createGestureController, createKeyboardController, createWheelController. Plain closures, no this binding issues, better tree-shaking.

@reelkit/react bridges the core to React. The <Reel> component creates a SliderController once via useState initializer and never recreates it. <ReelIndicator> renders Instagram-style scrollable dot indicators.

Reel Player

A ready-made TikTok/Instagram Reels overlay:

import { ReelPlayerOverlay } from '@reelkit/react-reel-player';
import '@reelkit/react-reel-player/styles.css';

<ReelPlayerOverlay
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  content={items}
  initialIndex={0}
/>
Enter fullscreen mode Exit fullscreen mode

Videos autoplay when the slide becomes active, pause when swiped away. A shared video element is reused across slides for iOS sound continuity.

Lightbox

Full-screen image gallery with three transition modes (slide, fade, zoom-in), swipe-to-close, keyboard navigation, and fullscreen API:

import { LightboxOverlay } from '@reelkit/react-lightbox';
import '@reelkit/react-lightbox/styles.css';

<LightboxOverlay
  isOpen={index !== null}
  images={images}
  initialIndex={index ?? 0}
  onClose={() => setIndex(null)}
  transition="fade"
/>
Enter fullscreen mode Exit fullscreen mode

Video support is opt-in and tree-shakeable — image-only usage pays zero extra cost.

Both packages expose render props for controls, navigation, and slide content — replace anything you need.

Links

If you're building a vertical feed, a reel player, or a gallery lightbox in React — give ReelKit a try. MIT licensed, open source.

Feedback, suggestions, and bug reports are welcome — open an issue or drop a comment below. And if ReelKit saved you some time, a GitHub star would mean a lot — it's a small thing, but it really helps the project get noticed.

Top comments (0)