Vue API Reference

Complete reference for @reelkit/vue components, composables, and utilities.

Reel

Tag: <Reel>

Props

ReelProps

PropTypeDefaultDescription
countnumberrequiredTotal number of slides
direction'vertical' | 'horizontal''vertical'Scroll direction
size[number, number] | undefinedundefinedWidth and height as [width, height]. When omitted, auto-measures via ResizeObserver
initialIndexnumber0Starting slide index
loopbooleanfalseEnable infinite loop
transitionTransitionTransformFnslideTransitionTransition effect function. Built-in: slideTransition, fadeTransition, flipTransition, cubeTransition, zoomTransition
transitionDurationnumber300Animation duration in ms
swipeDistanceFactornumber0.12Swipe threshold (0-1)
enableGesturesbooleantrueEnable touch/mouse drag navigation
enableNavKeysbooleantrueEnable keyboard arrow key navigation
enableWheelbooleanfalseEnable mouse wheel navigation
wheelDebounceMsnumber200Wheel event debounce in ms
rangeExtractor(index: number, count: number) => number[]defaultRangeExtractorCustom function to determine which indexes are rendered
keyExtractor(index: number, indexInRange: number) => stringindex => index.toString()Custom key function for slide rendering (useful with loop)
ariaLabelstringundefinedAccessible label for the carousel region
reelStyleRecord<string, string | number>undefinedInline styles applied to the root container element
reelClassstring | Array | ObjectundefinedCSS class(es) applied to the root container element
onNavKeyPress(increment: -1 | 1) => voidundefinedCallback prop that replaces the default ArrowUp/ArrowDown navigation. When provided, you implement your own navigation (e.g. call reelRef.value.next()). Omit for default behavior.

Events

EventPayloadDescription
beforeChange(index: number, nextIndex: number, indexInRange: number)Emitted before slide transition begins
afterChange(index: number, indexInRange: number)Emitted after slide transition completes
slideDragStart(index: number)Emitted when a drag gesture starts
slideDragEnd(index: number)Emitted when a drag gesture ends (released)
slideDragCanceled(index: number)Emitted when a drag gesture is canceled (snap-back)
tap(event: GestureCommonEvent)Emitted on a single tap gesture
doubleTap(event: GestureCommonEvent)Emitted on a double tap gesture
longPress(event: GestureCommonEvent)Emitted when a long press gesture starts
longPressEnd(event: GestureEvent)Emitted when a long press gesture ends

Slots

vue-html
<Reel :count="items.length">
  <template #item="{ index, indexInRange, size }">
    <!-- index       : number         — absolute slide index (0 to count-1) -->
    <!-- indexInRange : number         — position in the visible window (0, 1, or 2) -->
    <!-- size         : [number,number] — [width, height] of the container -->
    <MySlide :index="index" :size="size" />
  </template>

  <!-- default slot: overlay content rendered on top of the slides -->
  <ReelIndicator />
</Reel>
SlotScoped PropsDescription
#item{ index: number, indexInRange: number, size: [number, number] }Renders each visible slide. Called for each index in the virtualized range
defaultnoneOverlay content rendered on top of all slides (indicators, controls, etc.)

ReelExpose

Imperative API exposed via template ref:

vue
<script setup lang="ts">
import { ref } from 'vue';
import { Reel, type ReelExpose } from '@reelkit/vue';

const reelRef = ref<ReelExpose | null>(null);

function prev()  { reelRef.value?.prev(); }
function next()  { reelRef.value?.next(); }
function jump(i: number) { reelRef.value?.goTo(i, true); }
</script>

<template>
  <Reel ref="reelRef" :count="100">
    <template #item="{ index }">
      <div>Slide {{ index }}</div>
    </template>
  </Reel>
</template>
MethodTypeDescription
next()() => voidGo to next slide
prev()() => voidGo to previous slide
goTo(index, animate?)(number, boolean?) => Promise<void>Navigate to a specific slide index
adjust()() => voidRecalculate slide positions (useful after layout change)
observe()() => voidStart listening to gesture, keyboard, and wheel events
unobserve()() => voidStop listening to gesture, keyboard, and wheel events

ReelIndicator

Tag: <ReelIndicator>

Props

ReelIndicatorProps

PropTypeDefaultDescription
countnumber | undefinedautoTotal number of items. Auto-connected from parent Reel context when nested inside one; pass explicitly when used standalone
activenumber | undefinedautoCurrent active index. Auto-connected from parent Reel context when nested inside one; pass explicitly when used standalone
direction'vertical' | 'horizontal''vertical'Indicator orientation
radiusnumber3Dot radius in pixels
visiblenumber5Max normal-sized dots visible at once
gapnumber4Space between dots in pixels
activeColorstring'#fff'Active dot color
inactiveColorstring'rgba(255, 255, 255, 0.5)'Inactive dot color
edgeScalenumber0.5Scale factor for edge overflow dots
onDotClick(index: number) => voidundefinedCustom click handler. When omitted inside a Reel, defaults to navigating to the clicked dot index
indicatorClassstring | Array | ObjectundefinedCSS class(es) applied to the tablist root element
indicatorStyleCSSPropertiesundefinedInline styles merged into the tablist root element

Events

EventPayloadDescription
dotClick(index: number)Emitted when a dot is clicked; provides the dot index

SwipeToClose

Tag: <SwipeToClose> — Wraps its default slot in a touch-aware container that can be swiped to dismiss.

Props

SwipeToCloseProps

PropTypeDefaultDescription
direction'up' | 'down'requiredSwipe direction to trigger close. Use "up" for lightbox dismiss, "down" for stories dismiss
enabledbooleantrueWhether the swipe-to-close gesture is active
thresholdnumber0.2Fraction of viewport height required to trigger close (0-1)

Events

EventPayloadDescription
close()Emitted when the swipe gesture exceeds the threshold and the close animation completes

Slots

SlotDescription
defaultContent to wrap with swipe-to-close gesture handling

RK_REEL_KEY & useReelContext

An InjectionKey<ReelContextValue> provided by <Reel> to its descendants. Used internally by <ReelIndicator> for auto-connect behavior. Use useReelContext() in custom components that need slider context.

vue
<script setup lang="ts">
import { useReelContext } from '@reelkit/vue';

const ctx = useReelContext();

function jump(index: number) {
  ctx?.goTo(index, true);
}
</script>
PropertyTypeDescription
indexSignal<number>Reactive current slide index
countSignal<number>Reactive total item count
goTo(index: number, animate?: boolean) => Promise<void>Programmatically navigate to a slide

Composables

useBodyLock

Locks the document body scroll when the provided value is true. Uses reference counting so multiple concurrent callers can each lock/unlock independently. Automatically unlocks on unmount.

typescript
import { ref } from 'vue';
import { useBodyLock } from '@reelkit/vue';

const isOpen = ref(false);
useBodyLock(isOpen);

// Also accepts a static boolean
useBodyLock(true);
ParameterTypeDescription
lockedRef<boolean> | booleanWhether body scroll should be locked. Accepts a reactive ref or a static boolean

useFullscreen

UseFullscreenOptionsUseFullscreenReturn

Composable for managing the Fullscreen API with cross-browser support. Exits fullscreen automatically on unmount.

typescript
import { ref } from 'vue';
import { useFullscreen } from '@reelkit/vue';

const containerRef = ref<HTMLElement | null>(null);
const { isFullscreen, request, exit, toggle } = useFullscreen({
  elementRef: containerRef,
});
ReturnTypeDescription
isFullscreenSignal<boolean>Core signal reflecting current fullscreen state (read .value)
request() => Promise<void>Request fullscreen on the referenced element. If another element is already fullscreen, it is exited first (awaited).
exit() => Promise<void>Exit fullscreen
toggle() => Promise<void>Toggle fullscreen state

useSoundState

Access the current SoundController from context. Must be called inside a <SoundProvider>. Throws if called outside.

typescript
import { useSoundState } from '@reelkit/vue';

// Inside a SoundProvider descendant
const sound = useSoundState();

sound.muted;    // Signal<boolean>
sound.toggle(); // Toggle muted state

useOverlayUrlState

OverlayUrlStateOptions

Builds a URL-state controller for an overlay, which you hand to a <LightboxUrlOverlay> as its :controller prop.

See URL State in the Vue guide for the walkthrough and examples.

OptionTypeDefaultDescription
paramstringrequiredQuery parameter carrying the active slide, e.g. "photo".
adapterUrlAdapterHistory APINavigation system to read and write through. Pass a router-backed adapter in a routed app so the router's own location does not go stale.
codec{ decode(raw) => Id | null; encode(id) => string }requiredWire format: parameter text ↔ a stable identity, collection-blind. Travels with locator as a matched pair sharing the same Id — spread ...urlIndexKey(() => props.images.length) for the default ?photo=3 index gallery, or supply your own (base64, slug) so a bookmark survives the gallery being reordered.
locator{ locate(id) => number | null; locateAsync?(id) => Promise<number | null>; identify(index) => id }requiredMaps the identity to a position and owns its own validity: locate (sync), locateAsync (async fallback for a paginated gallery), identify (writes). For a plain index gallery spread ...urlIndexKey(() => props.images.length) — it supplies this locator plus the matching codec and bounds ?photo=3 against the live count, so a stale ?photo=99 heals out of the URL instead of opening a slide that was never named. Pass a getter, not a number, because a Vue setup runs once and a captured length would go stale as a paginated feed grows. A paginated feed or an identity-keyed gallery supplies its own matched codec + locator instead.

useVueRouterUrlAdapter

A UrlAdapter backed by Vue Router. Pass it as the adapter option of useOverlayUrlState in a routed app so the router stays the single source of navigation truth — writing history.pushState behind the router leaves its location stale and its next navigation drops the parameter. Writes touch the query only, so the path, the hash, and repeated keys such as ?tag=a&tag=b ride along untouched. Every change reports whether the router pushed on the same page, replaced, or stepped through history, so a gallery opened from a <router-link> closes with one back step.

Ships from its own subpath, so an app without a router never pulls vue-router into its bundle. vue-router is an optional peer dependency, 4.1 or later: the adapter carries its ownership stamp through the router's state navigation option, which older releases ignore. On an older router nothing breaks — closing just clears the parameter in place instead of stepping back.

typescript
import { useVueRouterUrlAdapter } from '@reelkit/vue/vue-router-url-adapter';

const adapter = useVueRouterUrlAdapter();
const photo = useOverlayUrlState({
  param: 'photo',
  adapter,
  ...urlIndexKey(() => props.images.length),
});

toVueRef

Bridges a core Subscribable (any Signal from @reelkit/core) into a read-only Vue Ref. Use it whenever you need a core signal value to drive a Vue re-render — direct signal.value reads in render functions or templates are not reactive on their own.

The subscription is auto-disposed via onScopeDispose, so this must be called inside a Vue setup() or other effect-scope-aware context.

typescript
import { defineComponent, h } from 'vue';
import { toVueRef, useSoundState } from '@reelkit/vue';

export const MuteIcon = defineComponent({
  setup() {
    const sound = useSoundState();
    const muted = toVueRef(sound.muted); // Readonly<Ref<boolean>>

    return () => h('span', muted.value ? '🔇' : '🔊');
  },
});

Observe

Tag: <Observe :signals="[…]"> — re-renders its default slot whenever one of the signals changes, and nothing around it.

Reading a core signal in a render function or template does not subscribe to it. toVueRef is the usual answer, but it binds the signal to the component that calls it, so a signal that changes often re-renders that whole component. Observe is the narrower boundary: put it around the part that shows the value, and the rest of the component stays put. It is what the stories player uses to repaint its header and progress bar without redrawing either slider.

PropTypeDescription
signalsreadonly Subscribable[]Signals to follow. Handing over a different set swaps the subscription; the same set keeps it. Everything is released on unmount.

The props type is exported as ObserveProps.

vue
<script setup lang="ts">
import { Observe, toVueRef } from '@reelkit/vue';
import { createTimerController } from '@reelkit/stories-core';

const timer = createTimerController({ duration: 5000 });

// toVueRef binds the signal to THIS component: every tick re-renders all of
// it. Fine for a small component, wasteful when something heavy sits beside
// the part that changes.
const progress = toVueRef(timer.progress);
</script>

<template>
  <section>
    <!-- Only the slot below re-renders on a tick; the list keeps its DOM. -->
    <Observe :signals="[timer.progress]">
      <progress :value="timer.progress.value" max="1" />
    </Observe>

    <ExpensiveList :items="items" />

    <!-- The same value through toVueRef, for comparison. -->
    <p>{{ Math.round(progress * 100) }}%</p>
  </section>
</template>

Reach for toVueRef when the whole component follows the signal, and Observe when only a region of it does.

SoundProvider

Tag: <SoundProvider> — Context provider that creates a SoundController instance and provides it to descendants via RK_SOUND_KEY. Renders its default slot transparently.

vue
<template>
  <SoundProvider>
    <Reel :count="items.length">
      <template #item="{ index }">
        <VideoSlide :index="index" />
      </template>
      <MuteButton />
    </Reel>
  </SoundProvider>
</template>

Accessibility

<Reel> renders as role="region" with aria-roledescription="carousel". Pass aria-label (the prop is ariaLabel in TS) to give the region a screen-reader name. A polite live region announces "Slide N of M" on every slide change. Inactive slides receive the inert attribute so focus and AT navigation skip them.

<ReelIndicator> renders as role="tablist" with roving tabindex on the dots; arrow keys move focus and Enter or Space activates the slide.

Building a custom modal around <Reel>? captureFocusForReturn, createFocusTrap, and getFocusableElements are re-exported from @reelkit/vue for focus return and trap.

Package Exports

All public exports from @reelkit/vue:

typescript
// Components
import {
  Reel,
  ReelIndicator,
  SwipeToClose,
  SoundProvider,
  Observe,
} from '@reelkit/vue';

// Types
import type {
  ReelExpose,
  ReelContextValue,
  SwipeToCloseDirection,
  SwipeToCloseProps,
  UseFullscreenOptions,
  UseFullscreenReturn,
} from '@reelkit/vue';

// Context & composables
import {
  RK_REEL_KEY,
  useReelContext,
  RK_SOUND_KEY,
  useBodyLock,
  useFullscreen,
  useSoundState,
  toVueRef,
} from '@reelkit/vue';

// Utilities (re-exported from @reelkit/core)
import {
  createDefaultKeyExtractorForLoop,
  defaultRangeExtractor,
} from '@reelkit/vue';

// Core re-exports
import {
  // Signals & reactivity
  createSignal, createComputed, reaction, batch, createDeferred,

  // Transitions
  slideTransition, fadeTransition, flipTransition,
  cubeTransition, zoomTransition, getSlideProgress,

  // Content loading & preloading
  createContentLoadingController, createContentPreloader,
  observeMediaLoading,

  // Sound
  createSoundController, syncMutedToVideo,

  // Fullscreen
  fullscreenSignal, requestFullscreen, exitFullscreen,

  // DOM & cleanup
  observeDomEvent, createDisposableList, createBodyLock, sharedBodyLock,

  // Focus management
  captureFocusForReturn, createFocusTrap, getFocusableElements,

  // Gestures
  createGestureController,

  // Video
  captureFrame, createSharedVideo,

  // Animation
  animate,

  // Utilities
  noop, clamp, abs, first, last, extractRange,
  lerp, isNegative, generate,
} from '@reelkit/vue';