ReelKit: A Virtualized TikTok-Style Slider Engine

ReelKit: A Virtualized TikTok-Style Slider Engine

posted Originally published at dev.to 4 min read

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
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>
  );
}

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>

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

With loop mode, it wraps at boundaries:

Current index: 0, loop: true
Visible: [9999, 0, 1]  ← seamless wrap

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}
/>

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

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"
/>

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.

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.

More Posts

TypeScript Complexity Has Finally Reached the Point of Total Absurdity

Karol Modelskiverified - Apr 23

I’m a Senior Dev and I’ve Forgotten How to Think Without a Prompt

Karol Modelskiverified - Mar 19

How I Built a React Portfolio in 7 Days That Landed ₹1.2L in Freelance Work

Dharanidharan - Feb 9

Sovereign Intelligence: The Complete 25,000 Word Blueprint (Download)

Pocket Portfolioverified - Apr 1

Merancang Backend Bisnis ISP: API Pelanggan, Paket Internet, Invoice, dan Tiket Support

Masbadar - Mar 13
chevron_left

Related Jobs

View all jobs →

Commenters (This Week)

4 comments
2 comments
2 comments

Contribute meaningful comments to climb the leaderboard and earn badges!