@reelkit/vue-reel-player

Full-screen vertical-swipe video/image reel player for Vue 3, inspired by Instagram Reels and TikTok
1,645 43
Install
npm install @reelkit/vue-reel-player
Latest Version:0.5.2
Vue:3
License:MIT
Last Updated:Oct 22, 2026
Links: GitHub  ·  npm
Maintainer: konstantin.kai

@reelkit/vue-reel-player

npm Bundle size Statement coverage Star on GitHub

A full-screen reel player on a phone: a swipe up moves to a three-photo post, a swipe sideways shows its next photo, and a swipe down returns to the first post.

Instagram Reels / TikTok-style video player for Vue 3. Opens as a full-screen overlay with vertical swipe navigation. Handles video autoplay, sound continuity on iOS, and multi-media posts. ~6.2 kB gzip.

Live Demo

Installation

npm install @reelkit/vue-reel-player @reelkit/vue lucide-vue-next

Requires Vue 3.3 or later.

Quick Start

<script setup lang="ts">
import { ref } from 'vue';
import { ReelPlayerOverlay, type ContentItem } from '@reelkit/vue-reel-player';
import '@reelkit/vue-reel-player/styles.css';

const isOpen = ref(false);
const content: ContentItem[] = [
  {
    id: '1',
    media: [
      {
        id: 'v1',
        type: 'video',
        src: 'https://example.com/video.mp4',
        poster: 'https://example.com/poster.jpg',
        aspectRatio: 9 / 16,
      },
    ],
    author: { name: 'John Doe', avatar: 'https://example.com/avatar.jpg' },
    likes: 1234,
    description: 'Amazing video!',
  },
];
</script>

<template>
  <button @click="isOpen = true">Open Player</button>
  <ReelPlayerOverlay
    :is-open="isOpen"
    :content="content"
    @close="isOpen = false"
  />
</template>

Features

  • Vertical swipe navigation (touch, mouse, keyboard, wheel)
  • Video autoplay with sound toggle
  • Multi-media posts with horizontal nested slider
  • Instagram-style indicator dots
  • Keyboard navigation (Arrow keys, Escape)
  • Desktop navigation arrows
  • iOS sound continuity via shared <video> element
  • Video position memory across slide changes
  • Playback timeline bar over long videos, scrubbable, with buffered ranges
  • Shareable URLs — ReelPlayerUrlOverlay opens itself from the address bar
  • Themeable via --rk-reel-* CSS custom properties

Scoped Slots

All of the player's UI is customizable via scoped slots:

Slot Scope
slide item, index, size, isActive, slideKey, defaultContent, …
slideOverlay item, index, isActive
controls item, soundState, activeIndex, content, onClose
navigation item, activeIndex, count, onPrev, onNext
nestedSlide item, media, index, size, isActive, isInnerActive, …
nestedNavigation media, activeIndex, count, onPrev, onNext
timeline item, activeIndex, timelineState, defaultContent
loading item, activeIndex
error item, activeIndex

Props

Prop Type Default Description
isOpen boolean required Controls overlay visibility
content ContentItem[] required Content items to display
initialIndex number 0 Starting slide index
initialInnerIndex number - Inner media index for the first slide only
aspectRatio number 9/16 Desktop container ratio
ariaLabel string 'Video player' Accessible label announced when the overlay opens
timeline 'auto' | 'always' | 'never' 'auto' When the built-in timeline bar renders
timelineMinDurationSeconds number 30 Under 'auto', videos shorter than this get no bar
loop boolean false Enable infinite loop
enableNavKeys boolean true Enable keyboard navigation
enableWheel boolean true Enable mouse wheel navigation
wheelDebounceMs number 200 Wheel debounce duration (ms)
transitionDuration number 300 Transition duration (ms)
swipeDistanceFactor number 0.12 Swipe threshold (0-1)

Events

Event Payload Description
close void Emitted when player closes
slide-change number Emitted after slide change
api-ready ReelPlayerApi Emitted with the imperative player API
update:is-open boolean Emitted on close — enables v-model:is-open

v-model:is-open

Use it to drive the overlay with a single binding instead of pairing :is-open + @close:

<script setup lang="ts">
import { ref } from 'vue';
const open = ref(false);
</script>

<template>
  <button @click="open = true">Open</button>
  <ReelPlayerOverlay v-model:is-open="open" :content="content" />
</template>

The legacy :is-open + @close pattern still works.

Custom content types

ReelPlayerOverlay and ReelPlayerUrlOverlay are generic over your content item shape. Extend BaseContentItem to use any data type: the item type is inferred from content, so passing anything else is a type error. Vue does not carry that type into a scoped slot, so annotate the slot binding with the exported slot-scope type to keep it strongly typed, for example #slideOverlay="{ item }: SlideOverlaySlotScope<MyItem>":

<script setup lang="ts">
import { ref } from 'vue';
import {
  ReelPlayerOverlay,
  type BaseContentItem,
  type SlideOverlaySlotScope,
} from '@reelkit/vue-reel-player';

interface MyItem extends BaseContentItem {
  title: string;
  category: 'video' | 'photo';
}

const open = ref(false);
const items: MyItem[] = [
  /* ... */
];
</script>

<template>
  <ReelPlayerOverlay v-model:is-open="open" :content="items">
    <template #slideOverlay="{ item }: SlideOverlaySlotScope<MyItem>">
      <div class="my-overlay">
        <h2>{{ item.title }}</h2>
        <span>{{ item.category }}</span>
      </div>
    </template>
  </ReelPlayerOverlay>
</template>

The same pattern works for every other slot — import the matching scope type (SlideSlotScope, ControlsSlotScope, NavigationSlotScope, NestedSlideSlotScope, LoadingSlotScope) and annotate the destructure.

URL-driven player

ReelPlayerUrlOverlay takes the same props except isOpen — the address bar owns the open state, so a shared link reopens the same post:

<script setup lang="ts">
import {
  ReelPlayerUrlOverlay,
  type ContentItem,
} from '@reelkit/vue-reel-player';
import { useOverlayUrlState, urlIndexKey } from '@reelkit/vue';
import { useVueRouterUrlAdapter } from '@reelkit/vue/vue-router-url-adapter';
import '@reelkit/vue-reel-player/styles.css';

const props = defineProps<{ content: ContentItem[] }>();

const reel = useOverlayUrlState({
  param: 'reel',
  adapter: useVueRouterUrlAdapter(),
  ...urlIndexKey(() => props.content.length),
});
</script>

<template>
  <!-- Opening is a link — the overlay reads the URL and opens itself. -->
  <RouterLink
    v-for="(post, i) in props.content"
    :key="post.id"
    :to="`?reel=${i}`"
  >
    <img :src="https://raw.githubusercontent.com/KonstantinKai/reelkit/HEAD/post.media[0].src" />
  </RouterLink>

  <ReelPlayerUrlOverlay :controller="reel" :content="props.content" />
</template>

Swap urlIndexKey for urlIndexTwoAxisKey to put the inner media index in the URL too (?reel=3.2), or urlStableIdKey to key it by post id so a bookmark survives the feed being reordered. Drop the adapter outside vue-router — the History API is the default.

Sub-components

For composing your own controls or slides: PlayerControls, CloseButton, SoundButton, ImageSlide, VideoSlide, SlideOverlay, TimelineBar, LoadingIndicator, ErrorIndicator, plus TimelineProvider with useTimelineState / useTimelineStateOptional to read playback state and useViewportSize for the desktop/mobile split.

Keyboard Shortcuts

Key Action
ArrowUp Previous slide
ArrowDown Next slide
ArrowLeft Previous media (nested)
ArrowRight Next media (nested)
Escape Close player

Documentation

Docs, runnable StackBlitz examples, and customization guides at reelkit.dev/docs/vue-reel-player.

Support

If ReelKit saved you some time, a star on GitHub would mean a lot — it's a small thing, but it really helps the project get noticed.

Star on GitHub

License

MIT

Related Packages

@reelkit/vue-lightbox

Full-screen image and video gallery lightbox for Vue 3 with touch gestures, tran...

1,394 43
@reelkit/vue-stories-player

Instagram-style stories player overlay for Vue 3 with cube transitions and auto-...

766 43
@reelkit/vue

Vue 3 components and composables for building TikTok/Instagram Reels-style slide...

1,950 43
vue-cat-carousel

vue-cat-carousel is a slider component of Vue.js.

2,408 14
vue-easy-slider

vue-easy-slider is a slider component of Vue.js.

79,751 356

Version History

Version Released Vue License
0.5.2 >=3.3.0 MIT
0.5.1 >=3.3.0 MIT
0.5.0 >=3.3.0 MIT
0.4.2 >=3.0.0 MIT
0.4.1 >=3.0.0 MIT