Stories Player

An Instagram-style stories player overlay for React using @reelkit/react-stories-player.

View live demo →

Features

Nested Navigation
Tap to advance stories, swipe to switch groups
Video Stories
Autoplay with sound toggle
Auto-Advance
Configurable timer per story
3D Transitions
Cube, flip, fade, zoom, slide
Progress Bar
Canvas-based segmented progress
Image & Video
Handles both media types
Virtualized
Only 3 slides in DOM
Double-Tap Like
Heart animation on double-tap
Desktop Nav
Chevron buttons on desktop
Story Rings
Instagram-style avatar rings
Generic Types
Extend StoryItem with custom data
Render Props
Customize every UI element

Installation

bash

Don't forget to import the styles:

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

Quick Start

The StoriesOverlay component renders a full-screen stories player. Pair it with StoriesRingList for Instagram-style entry points. Pass an array of StoriesGroup objects and control visibility with isOpen.

tsx

Live Demo

StoriesPlayer.tsx
Alice
Alice
Bob
Bob
Charlie
Charlie

Click a story ring to open the player. Tap left/right sides to navigate, swipe to switch users.

API Reference

StoriesOverlayProps

PropTypeDefaultDescription
isOpenbooleanrequiredControls overlay visibility. When true, body scroll is locked.
groupsStoriesGroup<T>[]requiredArray of story groups to display
onClose() => voidrequiredCallback to close the overlay
ariaLabelstring'Stories player'Accessible label for the dialog region; announced by screen readers when the overlay opens
initialGroupIndexnumber0Zero-based index of the initially visible group
initialStoryIndexnumber0Zero-based index of the initially visible story within the group
groupTransitionTransitionTransformFncubeTransitionTransition effect for the outer (group) slider
defaultImageDurationnumber5000Default auto-advance duration for image stories in milliseconds
tapZoneSplitnumber0.3Tap zone split ratio (0–1). Left portion triggers prev, right triggers next.
hideUIOnPausebooleantrueWhether to hide story UI (header, footer) when paused via long press
enableKeyboardbooleantrueEnable keyboard navigation (left/right arrows, Escape)
innerTransitionDurationnumber200Duration of the inner (story) transition animation in milliseconds
minSegmentWidthnumber8Minimum segment width in pixels for the progress bar
apiRefMutableRefObject<StoriesApi | null>-Ref to access the imperative StoriesApi
renderHeader(props: HeaderRenderProps<T>) => ReactNode-Custom header renderer. Receives author, story, pause/mute state.
renderFooter(props: FooterRenderProps<T>) => ReactNode-Custom footer renderer. Receives author and story info.
renderSlide(props: SlideRenderProps<T>) => ReactNode-Custom slide renderer, replacing the default image/video slides.
renderNavigation(props: NavigationRenderProps) => ReactNode-Custom desktop navigation. Replaces default prev/next chevron buttons.
renderProgressBar(props: ProgressBarRenderProps<T>) => ReactNode-Custom progress bar. Replaces default canvas progress bar.
renderLoading(props: LoadingRenderProps<T>) => ReactNode-Custom loading UI renderer. When not provided, shows default header spinner.
renderError(props: ErrorRenderProps<T>) => ReactNode-Custom error UI renderer. When not provided, shows default error icon overlay.

Callbacks

PropTypeDescription
onClose() => voidCalled when the overlay should close
onStoryChange(groupIndex: number, storyIndex: number) => voidFired when the active story changes
onGroupChange(groupIndex: number) => voidFired when the active group changes
onStoryViewed(groupIndex: number, storyIndex: number) => voidFired when a story becomes visible
onStoryComplete(groupIndex: number, storyIndex: number) => voidFired when a story's timer completes
onDoubleTap(groupIndex: number, storyIndex: number) => voidFired on a double-tap gesture
onPause() => voidFired when the player is paused
onResume() => voidFired when the player is resumed

Transitions

The groupTransition prop controls the 3D transition effect when swiping between user groups. Import transition functions from @reelkit/react:

tsx

Content Loading Lifecycle

Each story slide communicates its loading state via callbacks provided through SlideRenderProps:

CallbackWhen
onReadyContent is ready (image loaded, video playing). The progress timer starts.
onWaitingContent stalls (video buffering mid-playback). The spinner shows and timer pauses.
onErrorContent failed to load. The error overlay is shown.
onDurationReadyReport the actual media duration (e.g. from video metadata) to restart the timer with the correct duration.
onEndedSignal that the media has ended (e.g. video finished). Advances to the next story.
Preloader caching
The built-in ImageStorySlide and VideoStorySlide components preload the next story in the background. When a user navigates to a preloaded story, the content appears instantly without a loading spinner.

Render Props

Every UI element can be replaced via render props. Each receives typed props with all necessary state and callbacks.

renderHeader

Replace the default header (author info, pause/mute buttons, close button):

tsx

renderFooter

Add a footer below the story content:

tsx

renderSlide

Fully replace the default image/video slides. Use ImageStorySlide and VideoStorySlide sub-components for built-in media handling:

tsx

renderNavigation

Replace the default desktop chevron buttons:

tsx

renderProgressBar

Replace the default canvas progress bar with a custom implementation. The progress signal emits values from 0 to 1:

tsx

renderLoading

Custom loading indicator while content is being fetched:

tsx

renderError

Custom error overlay when content fails to load:

tsx

StoriesApi

Use the apiRef prop for imperative control:

tsx

Methods

MethodTypeDescription
nextStory()() => voidAdvance to the next story within the current group
prevStory()() => voidGo to the previous story within the current group
nextGroup()() => voidSwitch to the next user group
prevGroup()() => voidSwitch to the previous user group
goToGroup(index)(index: number) => voidJump to a specific group by index
pause()() => voidPause auto-advance and the progress timer
resume()() => voidResume auto-advance and the progress timer

Double-Tap & Likes

A built-in heart animation plays on double-tap, giving instant visual feedback. The onDoubleTap callback fires with the group and story index so you can persist the like in your own state (API call, local storage, etc.). The player does not manage like state internally.

tsx

Customizing the Heart Animation

Tweak the animation speed via the --rk-stories-heart-duration token (see Theming). For color, size, or hiding the heart entirely, target the .rk-stories-heart class directly. The HeartAnimation component is also exported for standalone use.

css
The built-in heart animation cannot be replaced via a render prop yet. You can restyle it with CSS or hide it with display: none and handle your own animation in the onDoubleTap callback. If you need a renderDoubleTap render prop, let us know via GitHub Issues.

Sub-Components

Reusable building blocks exported for composition in custom render props:

CanvasProgressBar

High-performance canvas-based segmented progress bar. Renders segments for each story and animates the active segment fill via requestAnimationFrame. Supports a sliding window for groups with many stories.

tsx

StoryHeader

Default header with author avatar, name, verified badge, relative timestamp, pause/play toggle, mute/unmute toggle, loading spinner, and close button. Used automatically when renderHeader is not provided.

tsx

ImageStorySlide

Full-bleed image slide with object-fit: cover. Reports load/error via callbacks for lifecycle tracking.

tsx

VideoStorySlide

Video slide using a shared <video> element for iOS sound continuity. Handles autoplay, poster frames, sound sync, and reports duration and playback lifecycle events.

tsx

StoriesRing

Circular avatar with an Instagram-style gradient ring. Segments indicate viewed/unviewed stories — gradient for unviewed, muted gray for viewed.

tsx

StoriesRingList

Horizontal scrollable row of StoriesRing components with author names. One ring per group.

tsx

HeartAnimation

Animated heart overlay triggered on double-tap. Scales up and fades out over 800ms. Customize via CSS (see Double-Tap & Likes section).

tsx

Types

StoryItem

typescript

AuthorInfo

typescript

StoriesGroup<T>

typescript

HeaderRenderProps<T>

typescript

FooterRenderProps<T>

typescript

SlideRenderProps<T>

typescript
typescript

ProgressBarRenderProps<T>

typescript

LoadingRenderProps<T>

typescript

ErrorRenderProps<T>

typescript

StoriesApi

typescript

Custom Story Types

Extend StoryItem with custom fields and pass the type parameter to StoriesOverlay. All render props will receive your extended type:

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-stories-player/styles.css. For color, size, and z-index changes, prefer the CSS custom properties documented in the Theming section below.

ClassComponentDescription
.rk-stories-overlayOverlayFixed full-screen backdrop (background, z-index)
.rk-stories-swipe-wrapperOverlaySwipe-to-close wrapper (hosts nav buttons + canvas)
.rk-stories-containerOverlayRounded story canvas (position, overflow)
.rk-stories-ui-layerOverlayUI overlay container (header, progress, navigation)
.rk-stories-ui-layer--hiddenOverlayUI hidden state (toggled by hideUIOnPause)
.rk-stories-errorOverlayError state (centered icon + text)
.rk-stories-error-textOverlayError message text
.rk-stories-nav-btnNavigationDesktop prev/next arrow
.rk-stories-progress-barProgressBarCanvas progress bar positioning wrapper
.rk-stories-slide-wrapperGroupOne group of stories (outer slide)
.rk-stories-storyStoryA single story (inner slide root)
.rk-stories-headerStoryHeaderHeader bar (avatar, name, actions)
.rk-stories-header--hiddenStoryHeaderHeader hidden state (visible=false)
.rk-stories-header-avatarStoryHeaderAuthor avatar image
.rk-stories-header-nameStoryHeaderAuthor name text
.rk-stories-header-verifiedStoryHeaderVerified badge container
.rk-stories-header-timeStoryHeaderTime-ago text
.rk-stories-header-actionsStoryHeaderRight-side actions (close, mute, pause)
.rk-stories-header-btnStoryHeaderHeader action button
.rk-stories-header-spinnerStoryHeaderVideo buffering spinner
.rk-stories-imageImageStorySlideImage story element
.rk-stories-videoVideoStorySlideVideo story container
.rk-stories-video-elementVideoStorySlideThe shared <video> element
.rk-stories-video-posterVideoStorySlideVideo poster image (fades out on play)
.rk-stories-video-poster--visibleVideoStorySlidePoster visible state (pre-playback)
.rk-stories-heartHeartAnimationDouble-tap heart pop animation
.rk-stories-ringStoriesRingStory ring (avatar with animated gradient border)
.rk-stories-ring--activeStoriesRingRing with unviewed stories (animates)
.rk-stories-ring-avatarStoriesRingAvatar image inside the ring
.rk-stories-ring-listStoriesRingListHorizontal ring list container
.rk-stories-ring-list-itemStoriesRingListRing + name column
.rk-stories-ring-list-nameStoriesRingListAuthor name below each ring

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-stories-overlay-bg#000Full-screen backdrop color
--rk-stories-overlay-z9999Overlay z-index
--rk-stories-container-radius12pxRounded corners on the story canvas (desktop)
--rk-stories-swipe-gap16pxGap between nav buttons and the story canvas
--rk-stories-top-shade-height120pxTop gradient scrim height behind the header
--rk-stories-top-shade-bglinear-gradient(to bottom, rgba(0,0,0,0.5) 0%, transparent 100%)Top gradient scrim color
--rk-stories-ui-transition200msFade duration when hideUIOnPause toggles
--rk-stories-nav-size44pxDesktop prev/next button size
--rk-stories-nav-bgrgba(255, 255, 255, 0.1)Desktop nav button background
--rk-stories-nav-bg-hoverrgba(255, 255, 255, 0.2)Desktop nav button hover background
--rk-stories-nav-fgrgba(255, 255, 255, 0.7)Desktop nav button icon color
--rk-stories-nav-fg-hover#fffDesktop nav button hover icon color
--rk-stories-error-bglinear-gradient(145deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)Error state background gradient
--rk-stories-error-fgrgba(255, 255, 255, 0.5)Error icon and text color
--rk-stories-error-text-size13pxError message font size
--rk-stories-video-bg#000Letterbox background behind <video>
--rk-stories-video-poster-transition200msPoster fade duration when the video starts playing
--rk-stories-header-top18pxVertical offset of the header from the top of the story
--rk-stories-header-padding12px 16pxInner padding of the header row
--rk-stories-header-avatar-size32pxAvatar width/height
--rk-stories-header-name-fg#fffAuthor name color
--rk-stories-header-name-size14pxAuthor name font size
--rk-stories-header-time-fgrgba(255, 255, 255, 0.6)Time-ago text color
--rk-stories-header-btn-fg#fffHeader action icon color (close, mute, pause)
--rk-stories-heart-duration800msPop-in/fade-out animation duration
--rk-stories-ring-spin-duration4sActive ring gradient rotation duration
--rk-stories-ring-list-gap12pxSpacing between rings in the list
--rk-stories-ring-list-padding12pxInner padding around the ring list
--rk-stories-ring-list-name-size12pxAuthor name font size below each ring

Drop the snippet below into a stylesheet loaded after @reelkit/react-stories-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 "Stories player".

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
ArrowLeftPrevious story
ArrowRightNext story
EscapeClose player