Stories Player
An Instagram-style stories player overlay for React using @reelkit/react-stories-player.
Features
Installation
Don't forget to import the styles:
Icons
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.
Live Demo
Click a story ring to open the player. Tap left/right sides to navigate, swipe to switch users.
API Reference
StoriesOverlayProps
| Prop | Type | Default | Description |
|---|---|---|---|
| isOpen | boolean | required | Controls overlay visibility. When true, body scroll is locked. |
| groups | StoriesGroup<T>[] | required | Array of story groups to display |
| onClose | () => void | required | Callback to close the overlay |
| ariaLabel | string | 'Stories player' | Accessible label for the dialog region; announced by screen readers when the overlay opens |
| initialGroupIndex | number | 0 | Zero-based index of the initially visible group |
| initialStoryIndex | number | 0 | Zero-based index of the initially visible story within the group |
| groupTransition | TransitionTransformFn | cubeTransition | Transition effect for the outer (group) slider |
| defaultImageDuration | number | 5000 | Default auto-advance duration for image stories in milliseconds |
| tapZoneSplit | number | 0.3 | Tap zone split ratio (0–1). Left portion triggers prev, right triggers next. |
| hideUIOnPause | boolean | true | Whether to hide story UI (header, footer) when paused via long press |
| enableKeyboard | boolean | true | Enable keyboard navigation (left/right arrows, Escape) |
| innerTransitionDuration | number | 200 | Duration of the inner (story) transition animation in milliseconds |
| minSegmentWidth | number | 8 | Minimum segment width in pixels for the progress bar |
| apiRef | MutableRefObject<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
| Prop | Type | Description |
|---|---|---|
| onClose | () => void | Called when the overlay should close |
| onStoryChange | (groupIndex: number, storyIndex: number) => void | Fired when the active story changes |
| onGroupChange | (groupIndex: number) => void | Fired when the active group changes |
| onStoryViewed | (groupIndex: number, storyIndex: number) => void | Fired when a story becomes visible |
| onStoryComplete | (groupIndex: number, storyIndex: number) => void | Fired when a story's timer completes |
| onDoubleTap | (groupIndex: number, storyIndex: number) => void | Fired on a double-tap gesture |
| onPause | () => void | Fired when the player is paused |
| onResume | () => void | Fired 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:
Content Loading Lifecycle
Each story slide communicates its loading state via callbacks provided through SlideRenderProps:
| Callback | When |
|---|---|
| onReady | Content is ready (image loaded, video playing). The progress timer starts. |
| onWaiting | Content stalls (video buffering mid-playback). The spinner shows and timer pauses. |
| onError | Content failed to load. The error overlay is shown. |
| onDurationReady | Report the actual media duration (e.g. from video metadata) to restart the timer with the correct duration. |
| onEnded | Signal that the media has ended (e.g. video finished). Advances to the next story. |
Preloader caching
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):
renderFooter
Add a footer below the story content:
renderSlide
Fully replace the default image/video slides. Use ImageStorySlide and VideoStorySlide sub-components for built-in media handling:
renderNavigation
Replace the default desktop chevron buttons:
renderProgressBar
Replace the default canvas progress bar with a custom implementation. The progress signal emits values from 0 to 1:
renderLoading
Custom loading indicator while content is being fetched:
renderError
Custom error overlay when content fails to load:
StoriesApi
Use the apiRef prop for imperative control:
Methods
| Method | Type | Description |
|---|---|---|
| nextStory() | () => void | Advance to the next story within the current group |
| prevStory() | () => void | Go to the previous story within the current group |
| nextGroup() | () => void | Switch to the next user group |
| prevGroup() | () => void | Switch to the previous user group |
| goToGroup(index) | (index: number) => void | Jump to a specific group by index |
| pause() | () => void | Pause auto-advance and the progress timer |
| resume() | () => void | Resume 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.
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.
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.
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.
ImageStorySlide
Full-bleed image slide with object-fit: cover. Reports load/error via callbacks for lifecycle tracking.
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.
StoriesRing
Circular avatar with an Instagram-style gradient ring. Segments indicate viewed/unviewed stories — gradient for unviewed, muted gray for viewed.
StoriesRingList
Horizontal scrollable row of StoriesRing components with author names. One ring per group.
HeartAnimation
Animated heart overlay triggered on double-tap. Scales up and fades out over 800ms. Customize via CSS (see Double-Tap & Likes section).
Types
StoryItem
AuthorInfo
StoriesGroup<T>
HeaderRenderProps<T>
FooterRenderProps<T>
SlideRenderProps<T>
NavigationRenderProps
ProgressBarRenderProps<T>
LoadingRenderProps<T>
ErrorRenderProps<T>
StoriesApi
Custom Story Types
Extend StoryItem with custom fields and pass the type parameter to StoriesOverlay. All render props will receive your extended type:
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.
| Class | Component | Description |
|---|---|---|
| .rk-stories-overlay | Overlay | Fixed full-screen backdrop (background, z-index) |
| .rk-stories-swipe-wrapper | Overlay | Swipe-to-close wrapper (hosts nav buttons + canvas) |
| .rk-stories-container | Overlay | Rounded story canvas (position, overflow) |
| .rk-stories-ui-layer | Overlay | UI overlay container (header, progress, navigation) |
| .rk-stories-ui-layer--hidden | Overlay | UI hidden state (toggled by hideUIOnPause) |
| .rk-stories-error | Overlay | Error state (centered icon + text) |
| .rk-stories-error-text | Overlay | Error message text |
| .rk-stories-nav-btn | Navigation | Desktop prev/next arrow |
| .rk-stories-progress-bar | ProgressBar | Canvas progress bar positioning wrapper |
| .rk-stories-slide-wrapper | Group | One group of stories (outer slide) |
| .rk-stories-story | Story | A single story (inner slide root) |
| .rk-stories-header | StoryHeader | Header bar (avatar, name, actions) |
| .rk-stories-header--hidden | StoryHeader | Header hidden state (visible=false) |
| .rk-stories-header-avatar | StoryHeader | Author avatar image |
| .rk-stories-header-name | StoryHeader | Author name text |
| .rk-stories-header-verified | StoryHeader | Verified badge container |
| .rk-stories-header-time | StoryHeader | Time-ago text |
| .rk-stories-header-actions | StoryHeader | Right-side actions (close, mute, pause) |
| .rk-stories-header-btn | StoryHeader | Header action button |
| .rk-stories-header-spinner | StoryHeader | Video buffering spinner |
| .rk-stories-image | ImageStorySlide | Image story element |
| .rk-stories-video | VideoStorySlide | Video story container |
| .rk-stories-video-element | VideoStorySlide | The shared <video> element |
| .rk-stories-video-poster | VideoStorySlide | Video poster image (fades out on play) |
| .rk-stories-video-poster--visible | VideoStorySlide | Poster visible state (pre-playback) |
| .rk-stories-heart | HeartAnimation | Double-tap heart pop animation |
| .rk-stories-ring | StoriesRing | Story ring (avatar with animated gradient border) |
| .rk-stories-ring--active | StoriesRing | Ring with unviewed stories (animates) |
| .rk-stories-ring-avatar | StoriesRing | Avatar image inside the ring |
| .rk-stories-ring-list | StoriesRingList | Horizontal ring list container |
| .rk-stories-ring-list-item | StoriesRingList | Ring + name column |
| .rk-stories-ring-list-name | StoriesRingList | Author 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.
| Token | Default | Controls |
|---|---|---|
| --rk-stories-overlay-bg | #000 | Full-screen backdrop color |
| --rk-stories-overlay-z | 9999 | Overlay z-index |
| --rk-stories-container-radius | 12px | Rounded corners on the story canvas (desktop) |
| --rk-stories-swipe-gap | 16px | Gap between nav buttons and the story canvas |
| --rk-stories-top-shade-height | 120px | Top gradient scrim height behind the header |
| --rk-stories-top-shade-bg | linear-gradient(to bottom, rgba(0,0,0,0.5) 0%, transparent 100%) | Top gradient scrim color |
| --rk-stories-ui-transition | 200ms | Fade duration when hideUIOnPause toggles |
| --rk-stories-nav-size | 44px | Desktop prev/next button size |
| --rk-stories-nav-bg | rgba(255, 255, 255, 0.1) | Desktop nav button background |
| --rk-stories-nav-bg-hover | rgba(255, 255, 255, 0.2) | Desktop nav button hover background |
| --rk-stories-nav-fg | rgba(255, 255, 255, 0.7) | Desktop nav button icon color |
| --rk-stories-nav-fg-hover | #fff | Desktop nav button hover icon color |
| --rk-stories-error-bg | linear-gradient(145deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%) | Error state background gradient |
| --rk-stories-error-fg | rgba(255, 255, 255, 0.5) | Error icon and text color |
| --rk-stories-error-text-size | 13px | Error message font size |
| --rk-stories-video-bg | #000 | Letterbox background behind <video> |
| --rk-stories-video-poster-transition | 200ms | Poster fade duration when the video starts playing |
| --rk-stories-header-top | 18px | Vertical offset of the header from the top of the story |
| --rk-stories-header-padding | 12px 16px | Inner padding of the header row |
| --rk-stories-header-avatar-size | 32px | Avatar width/height |
| --rk-stories-header-name-fg | #fff | Author name color |
| --rk-stories-header-name-size | 14px | Author name font size |
| --rk-stories-header-time-fg | rgba(255, 255, 255, 0.6) | Time-ago text color |
| --rk-stories-header-btn-fg | #fff | Header action icon color (close, mute, pause) |
| --rk-stories-heart-duration | 800ms | Pop-in/fade-out animation duration |
| --rk-stories-ring-spin-duration | 4s | Active ring gradient rotation duration |
| --rk-stories-ring-list-gap | 12px | Spacing between rings in the list |
| --rk-stories-ring-list-padding | 12px | Inner padding around the ring list |
| --rk-stories-ring-list-name-size | 12px | Author name font size below each ring |
Drop the snippet below into a stylesheet loaded after @reelkit/react-stories-player/styles.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
| Key | Action |
|---|---|
| ArrowLeft | Previous story |
| ArrowRight | Next story |
| Escape | Close player |