Reel Player

A full-screen Instagram-Reel/TikTok-style video player component using @reelkit/react-reel-player.

View live demo →

Features

Vertical Swipe
Touch, drag, keyboard, wheel
Video Autoplay
Plays when visible
Sound Toggle
iOS continuity
Multi-Media
Horizontal nested carousels
Position Memory
Resumes where left off
Frame Capture
Poster-to-video crossfade
Virtualized
Only 3 slides in DOM
Aspect Ratio
9:16 desktop, full mobile
Desktop Nav
Arrow buttons
Generic Types
Custom content data models
Customizable
Render props for everything
Slide Overlay
Author, likes, description

Installation

bash

Don't forget to import the styles:

typescript
Icons
The default controls use lucide-react for icons. If you prefer a different icon library, use renderControls and renderNavigation to provide your own.

Quick Start

The ReelPlayerOverlay component renders a full-screen player overlay. Pass an array of ContentItem objects and control visibility with isOpen.

tsx

Live Demo

ReelPlayerPage.tsx

Click a thumbnail to open the full-screen player. Press Escape or the close button to return.

Customization

Generic Content Type

Use custom data types by extending BaseContentItem:

tsx

Custom Slide Overlay

Replace the built-in slide overlay with custom content per slide:

tsx

Non-Media Slides

Use renderSlide to inject custom content (e.g., CTA cards). Return null to fall back to default:

tsx

Custom Controls

Compose reusable sub-components with your own additions:

tsx

Custom Timeline

Replace the built-in playback bar with your own scrub UI via renderTimeline. The callback only fires when the overlay's gating rules would render the default bar (same timeline mode + timelineMinDurationSeconds logic), so you don't re-implement it. Reuse the .rk-reel-timeline class on your root to inherit flush-bottom positioning, safe-area padding, and touch-device slide-overlay clearance.

tsx

Custom Navigation

tsx

Custom Nested Navigation

Replace the left/right arrows inside multi-media slides (horizontal carousel) with custom navigation:

tsx

Custom Nested Slides

Customise individual slides inside multi-media carousels with renderNestedSlide. Use props.defaultContent to wrap the default ImageSlide/VideoSlide, or replace it entirely:

tsx

API Reference

Props

PropTypeDefaultDescription
apiRefMutableRefObject<ReelApi>-Ref to access Reel API
ariaLabelstring'Video player'Accessible label for the dialog region; announced by screen readers when the overlay opens
aspectRationumber9/16 (0.5625)Width/height ratio for the player container on desktop. On mobile the player always uses full viewport.
contentT[]requiredArray of content items (generic, defaults to ContentItem)
initialIndexnumber0Starting slide index
isOpenbooleanrequiredControls overlay visibility
timeline'auto' | 'always' | 'never''auto'Gating strategy for the built-in playback timeline bar. 'auto' renders only for videos longer than timelineMinDurationSeconds; 'always' renders whenever the active slide has a video; 'never' disables the built-in bar (use renderTimeline for a fully custom replacement).
timelineMinDurationSecondsnumber30Minimum video duration (seconds) for timeline='auto' to render the built-in bar. Short looping clips below this threshold are suppressed.
renderControls(props: ControlsRenderProps) => ReactNode-Custom controls, replaces default close + sound buttons
renderError(props: { item: T; activeIndex: number }) => ReactNode-Custom error indicator, replaces default error icon
renderLoading(props: { item: T; activeIndex: number }) => ReactNode-Custom loading indicator, replaces default wave loader
renderNavigation(props: NavigationRenderProps) => ReactNode-Custom navigation, replaces default vertical arrows
renderNestedNavigation(props: NavigationRenderProps) => ReactNode-Custom navigation for nested horizontal slider (multi-media posts), replaces default left/right arrows
renderNestedSlide(props: NestedSlideRenderProps) => ReactNode-Custom slide renderer for nested horizontal slider items. Use props.defaultContent to wrap or embed the default ImageSlide/VideoSlide. Unlike renderSlide, null is not treated as a fallback.
renderSlide(props: SlideRenderProps) => ReactNode | null-Custom slide rendering. Return null to fall back to default. Use props.defaultContent to wrap or embed the default slide.
renderSlideOverlay(item, index, isActive) => ReactNode-Custom overlay per slide, replaces default SlideOverlay. Return null to hide.
renderTimeline(props: TimelineRenderProps) => ReactNode-Custom playback timeline bar. Invoked only when gating rules would render the default bar (same auto/always/never + timelineMinDurationSeconds logic). Use props.defaultContent to wrap the built-in <TimelineBar />; return null to hide.

Callbacks

PropTypeDescription
onClose() => voidCalled when player closes
onSlideChange(index: number) => voidCalled after slide change

Reel Props (proxied)

These props are forwarded to the underlying Reel component.

PropTypeDefaultDescription
enableNavKeysbooleantrueEnable keyboard navigation
enableWheelbooleantrueEnable mouse wheel navigation
loopbooleanfalseEnable infinite loop
swipeDistanceFactornumber0.12Swipe threshold (0-1)
transitionDurationnumber300Transition animation duration (ms)
wheelDebounceMsnumber200Wheel debounce duration (ms)

Types

BaseContentItem

The generic constraint type. Extend this to use custom data types with ReelPlayerOverlay.

typescript

ContentItem

typescript

MediaItem

typescript

MediaType

typescript

ControlsRenderProps<T>

typescript
typescript

SlideRenderProps<T>

typescript

NestedSlideRenderProps

typescript

SlideOverlayProps

typescript

ImageSlideProps

typescript

VideoSlideProps

typescript

CloseButtonProps

typescript

SoundButtonProps

typescript

TimelineBarProps

typescript

TimelineRenderProps<T>

typescript

Sub-Components

Reusable building blocks exported for composition in custom render props:

CloseButton

Standalone close button with default reel-player styling. Use inside renderControls.

tsx

SoundButton

Standalone sound toggle. Must be inside a SoundProvider (automatically provided by ReelPlayerOverlay).

tsx

TimelineBar

Default playback scrub bar. Reads from the nearest TimelineProvider (automatically mounted inside ReelPlayerOverlay) and renders the track, buffered ranges, progress fill, and scrub pill. Theme via the --rk-reel-timeline-* custom properties, or replace via renderTimeline.

tsx

SlideOverlay

The default gradient overlay showing author, description, and likes. Rendered automatically when content has the required fields. Use renderSlideOverlay to replace or hide it.

tsx

ImageSlide

Image slide with lazy loading and object-fit: cover by default. Use inside renderSlide to compose custom image slides with your own styles.

tsx

VideoSlide

Video slide with shared <video> element for iOS sound continuity, poster frames, position memory, and loading indicator. Must be inside a SoundProvider (automatically provided by ReelPlayerOverlay).

tsx
Composing custom slides
Use renderSlide with ImageSlide / VideoSlide to customize media rendering while keeping all built-in behavior (autoplay, poster capture, sound sync).
tsx

Content Loading & Error Handling

The player tracks per-slide loading and error states. A wave loader shows while content loads; an error icon shows for broken media. Errored URLs are cached so revisiting shows the error instantly without retrying.

Lifecycle Callbacks

When using renderSlide, call these callbacks to control the loading indicator:

CallbackWhen to call
onReadyImage loaded or video started playing. Clears loading and error states.
onWaitingVideo is buffering mid-playback. Shows the loading indicator.
onErrorContent failed to load. Shows error overlay and caches the URL as broken.
tsx

Custom Loading & Error UI

Replace the default wave loader and error icon with custom components:

tsx

Timeline

The overlay renders a built-in playback timeline bar over the active video. The timeline prop gates rendering:

  • 'auto' (default): renders when the active media is a video whose duration exceeds timelineMinDurationSeconds (default 30). Works for single-video slides and multi-media carousels; the bar follows the active nested item and hides on images.
  • 'always': renders whenever the active slide has a video.
  • 'never': never renders. Build a custom bar via renderTimeline.
tsx

Theme via the --rk-reel-timeline-* CSS custom properties (height, colors, cursor size). For a fully custom scrub bar, timecode, or progress indicator, use renderTimeline; the callback receives a timelineState backed by the underlying TimelineController.

Sound Context

For custom implementations, you can access the sound state:

tsx

CSS Classes

All CSS classes are plain (not CSS modules), so they can be targeted with higher-specificity selectors in a stylesheet loaded after @reelkit/react-reel-player/styles.css. For color, size, and z-index changes, prefer the CSS custom properties documented in the Theming section below — they're designed for exactly that.

ClassComponentDescription
.rk-reel-overlayOverlayFixed full-screen backdrop (background, z-index)
.rk-reel-containerOverlayPlayer container (position, overflow)
.rk-reel-loaderOverlayWave loading animation overlay
.rk-reel-media-errorOverlayError state overlay (centered icon + text)
.rk-reel-media-error-textOverlayError message text
.rk-reel-buttonControlsShared circular icon button (close, sound, nav arrows)
.rk-reel-close-btnControlsClose button
.rk-reel-sound-btnControlsSound toggle button
.rk-reel-nav-arrowsNavigationDesktop-only arrow container (hidden below 768px)
.rk-reel-nav-buttonNavigationIndividual prev/next nav arrow
.rk-reel-slide-wrapperSlideWrapper around media + overlay
.rk-reel-slide-overlaySlideOverlayGradient overlay container
.rk-reel-slide-overlay-authorSlideOverlayAuthor row (avatar + name)
.rk-reel-slide-overlay-avatarSlideOverlayAuthor avatar image
.rk-reel-slide-overlay-nameSlideOverlayAuthor name text
.rk-reel-slide-overlay-descriptionSlideOverlayDescription text
.rk-reel-slide-overlay-likesSlideOverlayLikes row (heart + count)
.rk-reel-video-containerVideoSlideVideo wrapper (background, overflow)
.rk-reel-video-elementVideoSlideThe <video> element
.rk-reel-video-posterVideoSlidePoster image (fades out on play)
.rk-reel-video-poster.rk-visibleVideoSlideState modifier applied to the poster while the video is paused/loading
.rk-reel-nested-indicatorNestedSliderDot pagination under multi-media slides (position varies desktop vs. touch)
.rk-reel-nested-navNestedSliderHorizontal carousel arrows (hidden below 768px)
.rk-reel-nested-nav-nextNestedSliderNested next arrow position
.rk-reel-nested-nav-prevNestedSliderNested prev arrow position
.rk-reel-timelineTimelineBarScrub-bar wrapper. Reuse on custom `renderTimeline` roots to inherit flush-bottom positioning, safe-area padding, and touch-device slide-overlay clearance.
.rk-reel-timeline-trackTimelineBarTrack (unplayed region)
.rk-reel-timeline-bufferedTimelineBarBuffered segments layer
.rk-reel-timeline-fillTimelineBarPlayed-progress fill
.rk-reel-timeline-cursorTimelineBarScrub-handle pill (floats above the track)

Theming

Every color, size, z-index, and transition lives in a CSS custom property. Override one or many at :root (or any ancestor of the overlay) to retheme without touching component source.

TokenDefaultControls
--rk-reel-overlay-bg#000Full-screen backdrop color
--rk-reel-overlay-z1000Overlay z-index
--rk-reel-button-bgrgba(0, 0, 0, 0.5)Default circular button background
--rk-reel-button-bg-hoverrgba(255, 255, 255, 0.1)Nav arrow background (and base hover state)
--rk-reel-button-bg-hover-strongrgba(255, 255, 255, 0.2)Nav arrow hover background
--rk-reel-button-fg#fffButton icon color
--rk-reel-button-size44pxButton width / height
--rk-reel-button-radius50%Button border-radius
--rk-reel-ui-z10Close / sound / nav z-index
--rk-reel-edge-padding16pxEdge inset for close / sound / nav arrows
--rk-reel-nav-gap8pxSpacing between stacked nav arrows
--rk-reel-transition0.2sHover transition duration
--rk-reel-loader-colorrgba(255, 255, 255, 0.12)Wave loader gradient color
--rk-reel-loader-duration1.8sWave loader animation duration
--rk-reel-error-fgrgba(255, 255, 255, 0.4)Error icon and text color
--rk-reel-error-text-size13pxError message font size
--rk-reel-slide-overlay-bglinear-gradient(transparent, rgba(0, 0, 0, 0.7))Caption scrim gradient
--rk-reel-slide-overlay-padding48px 16px 16pxCaption inner padding
--rk-reel-slide-overlay-name-color#fffAuthor name color
--rk-reel-slide-overlay-description-colorrgba(255, 255, 255, 0.9)Description text color
--rk-reel-slide-overlay-likes-colorrgba(255, 255, 255, 0.8)Likes row text color
--rk-reel-video-bg#000Letterbox background behind <video>
--rk-reel-nested-button-bgrgba(0, 0, 0, 0.5)Nested arrow background
--rk-reel-nested-button-bg-hoverrgba(255, 255, 255, 0.2)Nested arrow hover background
--rk-reel-nested-button-size36pxNested arrow size
--rk-reel-nested-edge-padding12pxNested arrow edge inset
--rk-reel-timeline-trackrgba(255, 255, 255, 0.22)Track background (unplayed region)
--rk-reel-timeline-bufferedrgba(255, 255, 255, 0.4)Buffered segments color
--rk-reel-timeline-fill#fffPlayed-progress fill color
--rk-reel-timeline-cursor#fffScrub-handle pill color
--rk-reel-timeline-height3pxTrack height at rest
--rk-reel-timeline-height-active6pxTrack height on hover / focus / scrub
--rk-reel-timeline-cursor-width10pxScrub-pill width at rest
--rk-reel-timeline-cursor-width-active14pxScrub-pill width while scrubbing
--rk-reel-timeline-cursor-height24pxScrub-pill height at rest
--rk-reel-timeline-cursor-height-active32pxScrub-pill height while scrubbing
--rk-reel-timeline-hitbox16pxExtra pointer hit-area above the track
--rk-reel-timeline-transition0.15s ease-outTrack + pill grow/shrink animation
--rk-reel-timeline-z11Timeline z-index (above the default UI layer)

Drop the snippet below into a stylesheet loaded after @reelkit/react-reel-player/styles.css.

css

Accessibility

The overlay root is a modal dialog (role="dialog", aria-modal="true"). Set ariaLabel to change the screen-reader announcement; it defaults to "Video player". Each slide carries role="group", aria-roledescription="slide", and an aria-label="Slide N of M", so swiping announces position in the sequence.

The overlay captures focus on open and returns it to the trigger on close. Tab and Shift+Tab cycle through focusable elements inside; focus that escapes (click outside, programmatic focus) gets pulled back. Implemented with captureFocusForReturn and createFocusTrap from @reelkit/core.

Keyboard Shortcuts

KeyAction
ArrowUpPrevious slide
ArrowDownNext slide
ArrowLeftPrevious media (in nested slider)
ArrowRightNext media (in nested slider)
EscapeClose player