# react-kino 0.6.0
Document schema version 1. Pure data: react-kino/document and react-kino/recipes. React client entries: react-kino/story, react-kino/studio, react-kino/recipe-kit. CLI: @react-kino/cli 0.2.0. MIT licensed.
---
# Storyboard and portable stories (https://www.react-kino.dev/docs/authoring)
Edit real React scroll stories visually, export a versioned JSON document, and keep your components in your codebase.
Kino Storyboard is an optional, local visual editor. Open [the workspace](/studio), choose a recipe, and select a layer in the preview or timeline. Drag its start/end handles or use keyboard arrows and numeric fields. Adjust its transform, reorder layers or scenes, and compare the desktop animation with the mobile reading layout.
This authoring API requires `react-kino@0.6.0` or later.
## Render the document
```tsx
"use client";
import { Story } from "react-kino/story";
import { parseStory } from "react-kino/document";
import { createRecipeComponents } from "react-kino/recipe-kit";
import rawStory from "./story.kino.json";
const document = parseStory(JSON.stringify(rawStory));
export function MyPage() {
return (
,
}}
/>
);
}
```
A component layer references a registered name. The document never contains executable React or JavaScript. Your application supplies links, forms, media, and rich UI through `components`; imported documents can only select those names. Missing registrations remain visible and produce a diagnostic.
## Embed the editor locally
```tsx
"use client";
import { Storyboard } from "react-kino/studio";
import { createStoryRecipe } from "react-kino/recipes";
import { createRecipeComponents } from "react-kino/recipe-kit";
export function Authoring() {
return (
,
}}
onChange={(document) => console.log(document)}
/>
);
}
```
Import the editor only in your authoring route or development UI. The default runtime does not import it. The editor provides its own scoped styles. Undo/redo is bounded to 80 document states. Drafts are saved locally after edits; a restored draft requires your explicit choice. **Clear saved draft** removes the stored copy, keeping the current open document available for export.
Download JSON to share or commit. Import files or paste JSON in the inspector. Imports are limited to 2 MB and validated before replacing the current document; you can undo a successful import. Storage and clipboard failures are shown in the workspace, with download as the alternative. No account, hosted data store, telemetry, or payment is required.
## Mobile is a reading policy
A story narrower than 640px switches to normal document flow, one column, and visible untransformed layers. Reduced-motion users receive the same reading path. This is a deliberate responsive policy, not a smaller copy of the desktop animation. A tall desktop scene also unpins and reveals its content. Use `reading` to enable this mode explicitly.
## Validate and inspect
```bash
npx @react-kino/cli recipe launch story.kino.json
npx @react-kino/cli validate story.kino.json --json
npx @react-kino/cli doctor story.kino.json --components Product --json
```
The CLI checks the document, not the layout of your deployed application. Browser testing is still necessary for media, registered components, overflow, focus, and performance. The [JSON Schema](/schema/story-v1.json) helps editors; `validateStory` also checks unique IDs and start-before-end constraints.
## Support independent maintenance
Tourlight, Kino, Clickmap, and Redact share one independent maintainer. Voluntary support helps fund fixes, compatibility, documentation, and development; every feature remains free and MIT licensed. [Support this project](https://react-tourlight.vercel.app/support) through our shared support page. No sponsorship is needed to use or export your work.
---
# Getting Started (https://www.react-kino.dev/docs)
Install react-kino and build your first scroll experience in minutes.
## Installation
```bash
npm install react-kino
```
```bash
pnpm add react-kino
```
```bash
yarn add react-kino
```
**Requirements:** React 18+
## Quick Start
Wrap your app with ``, add a ``, and you have a pinned scroll section:
```tsx
import { Kino, Scene, Reveal, Counter } from "react-kino";
function App() {
return (
{(progress) => (
Welcome
Scroll-driven storytelling, made simple.
`${n.toLocaleString()}+ users`}
/>
)}
);
}
```
That is a complete scroll experience: the section pins in place, content fades in at different scroll points, and a number counts up -- all in ~20 lines.
## Why react-kino
- **Separate tools** -- runtime, pure documents, recipes and optional Storyboard have explicit imports.
- **Declarative** -- compose ``, ``, ``, ``, and `` like regular React components. No imperative timelines.
- **Lightweight runtime** -- `react-kino` uses a tiny internal engine package (`@kino/core`) plus React peers.
- **SSR-safe** -- every component renders children on the server and animates on the client.
## SSR / Next.js
react-kino is SSR-safe and defers scroll logic to `useEffect`.
```tsx
// app/page.tsx
"use client";
import { Kino, Scene, Reveal } from "react-kino";
export default function Page() {
return (
Works with App Router
);
}
```
**What happens on the server:** Components render their children immediately with no animation styles. Scroll tracking starts after hydration on the client.
## Accessibility
react-kino respects the `prefers-reduced-motion` media query:
- `` -- content renders immediately in its visible state
- `` -- parallax offset is disabled
- `` -- jumps to the `to` state immediately, no interpolation
- `` -- displays the final value immediately
No additional configuration is required.
## Animation Engine
react-kino uses a lightweight JavaScript scroll engine with CSS transforms and opacity transitions:
1. **Shared scroll tracker** -- one provider can drive all scroll subscribers.
2. **requestAnimationFrame batching** -- updates are coalesced for smoother rendering.
3. **GPU-friendly styles** -- reveal/parallax use transform and opacity.
## Browser Support
| Feature | Chrome | Firefox | Safari | Edge |
| ------------------------ | ------ | ------- | ------ | ---- |
| Core scroll tracking | 64+ | 60+ | 13+ | 79+ |
| `position: sticky` | 56+ | 59+ | 13+ | 79+ |
| `prefers-reduced-motion` | 74+ | 63+ | 10.1+ | 79+ |
## Author a complete story
Open [Storyboard](/studio) to edit a recipe in the actual renderer, or start with [portable documents](/docs/authoring). Read the [responsive layout policy](/docs/responsive-stories) before building pinned content.
---
# Responsive scroll stories that keep their content (https://www.react-kino.dev/docs/responsive-stories)
Handle tall pinned sections, nested scroll roots, reduced motion, local parallax, and changing content in React.
A pinned scene is a layout constraint: its content must fit the available viewport. Kino measures the scene content and falls back to natural flow when it is too tall. Late images, font changes, container resizing, and window resizing can change that decision.
```tsx
```
The default `overflow="auto"` policy preserves the whole article. Natural reading fallback advances the scene to its final progress, so late reveals remain visible. `overflow="clip"` opts into the older deliberately clipped cinematic treatment. In Story documents, reading fallback additionally neutralizes layer transforms and opacity, including exit animations.
Reduced-motion users receive an unpinned scene without scroll-distance padding. Horizontal galleries become a vertical list. Do not put essential content behind animation-only callbacks.
## A nested scroll area
```tsx
const root = useRef(null);
return (
);
```
Keep the root ref attached for the provider lifetime. Scroll coordinates, viewport height, intersection gating, and resize invalidation use that root. `tracker.refresh()` is available through `useKino()` after application-specific layout changes.
## Text entering from outside the viewport
```tsx
This paragraph moves relative to its own passage through the viewport.
```
Both range values are pixels. Existing `speed` and `direction` props remain available, but speed now uses a local element origin rather than page-top displacement. This intentionally fixes accumulating offsets far down a page. Test existing compositions if they compensated for the old global behavior.
## Verify your story
Check 320px and 390px mobile layouts, keyboard traversal, 200% zoom, reduced motion, browser back/scroll restoration, and delayed media. Test tall content with a reveal near the end of the scene, not just the first heading. Browser engines and actual video encodings can behave differently; a jsdom test does not establish visual correctness.
---
# CompareSlider (https://www.react-kino.dev/docs/components/compare-slider)
Before/after comparison slider with drag and scroll-driven modes.
## Overview
`` creates a before/after comparison. Supports both interactive drag mode and scroll-driven mode when nested inside a ``.
## Usage
```tsx
import { CompareSlider } from "react-kino";
{/* Interactive drag mode */}
}
after={}
/>
{/* Scroll-driven mode inside a Scene */}
}
after={}
/>
```
## Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `before` | `ReactNode` | -- | Content shown on the "before" side (always visible underneath) |
| `after` | `ReactNode` | -- | Content shown on the "after" side (revealed via clip) |
| `scrollDriven` | `boolean` | `false` | If `true`, slider position follows scroll progress instead of drag |
| `progress` | `number` | -- | Progress override (0-1). When `scrollDriven`, defaults to parent `` context |
| `initialPosition` | `number` | `0.5` | Initial slider position (0-1) in drag mode |
| `ariaLabel` | `string` | `"Comparison slider"` | Accessible label for the drag handle (`role="slider"`) |
| `className` | `string` | -- | CSS class for the container |
The handle exposes `role="slider"` with `aria-valuenow`/`aria-valuemin`/`aria-valuemax`, and supports keyboard control (`ArrowLeft`/`ArrowRight` to nudge, `Home`/`End` to jump to the ends) when not `scrollDriven`.
## Example: Scroll-driven comparison
```tsx
}
after={
}
/>
```
---
# Counter (https://www.react-kino.dev/docs/components/counter)
Animated number that counts between two values as the user scrolls.
## Overview
`` animates a number between two values as the user scrolls. Automatically reads progress from a parent ``, or accepts a `progress` prop directly.
## Usage
```tsx
import { Scene, Counter } from "react-kino";
`${n.toFixed(1)}%`}
easing="ease-in-out"
/>
```
## Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `from` | `number` | -- | Starting value |
| `to` | `number` | -- | Ending value |
| `at` | `number` | `0` | Progress value (0-1) when counting begins |
| `span` | `number` | `0.3` | How much of the progress range (0-1) the count spans |
| `format` | `(value: number) => string` | `toLocaleString` | Formatting function for the displayed value |
| `easing` | `string \| (t: number) => number` | `"ease-out"` | Easing preset name or custom easing function |
| `progress` | `number` | -- | Direct progress override (0-1). If omitted, reads from parent `` context |
| `className` | `string` | -- | CSS class for the `` element |
When both `from` and `to` are integers, the displayed value is automatically rounded.
## Accessibility
When `prefers-reduced-motion` is enabled, the counter displays the final `to` value immediately once progress reaches the `at` threshold.
## Example: Stat counter section
```tsx
Countries
Users
`${n.toFixed(1)}%`} />
Uptime
```
---
# HorizontalScroll (https://www.react-kino.dev/docs/components/horizontal-scroll)
Converts vertical scroll into horizontal movement with full-viewport panels.
## Overview
`` converts vertical scroll into horizontal movement. Wrap `` components inside it to create horizontally-scrolling sections.
## Usage
```tsx
import { HorizontalScroll, Panel } from "react-kino";
Panel One
Panel Two
Panel Three
```
## `` Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `children` | `ReactNode` | -- | `` components |
| `className` | `string` | -- | CSS class for the outer spacer |
| `panelHeight` | `string` | `"100vh"` | Height of each panel as a CSS string |
## `` Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `children` | `ReactNode` | -- | Panel content |
| `className` | `string` | -- | CSS class |
| `style` | `CSSProperties` | -- | Inline styles (merged with default `100vw x 100vh` sizing) |
## How it works
The spacer height is automatically set to `childCount * 100vh`, giving each panel a full viewport of scroll distance. Vertical scroll is translated into a horizontal `translateX` on the inner container.
## Example: Feature showcase
```tsx
{features.map((f) => (
{f.title}
{f.description}
))}
```
---
# Kino (https://www.react-kino.dev/docs/components/kino)
Root provider that initializes the scroll tracking engine.
## Overview
`` is the root provider that initializes the scroll tracking engine. Wrap your app or page layout with it to enable scroll-driven animations.
## Usage
```tsx
import { Kino } from "react-kino";
{/* your scenes and content */}
```
All scroll-driven components (``, ``, `
}
>
The same explanation remains part of the page.
```
The default is metadata preload. Set `preload="none"` if you want to defer loading further, or `auto` only when the media cost is justified. Reduced-motion mode does not attach the video source and keeps the poster plus a readable overlay. Media errors remove the pinned spacer and show the fallback. A visible loading status remains until playable data arrives.
The seek loop waits for an in-flight seek and then applies the latest requested position. This prevents queuing every intermediate scroll frame, but it cannot make a poorly encoded or unavailable video scrub smoothly. Use a short muted video, sensible dimensions/bitrate, frequent keyframes, and an HTTP server that supports range requests. Test your actual file on Safari and mobile devices.
Keep critical text outside the pixels of the video. A poster needs a useful description, and any spoken content needs an accessible text or caption alternative. For an editorial sequence with text entering over a fixed visual, combine ordinary semantic text with Scene and explicit transform ranges rather than baking the words into the video.