Your first Wrapped
This page goes from an empty file to a video that plays: who it is about, a seed, three scenes and one component, with the settings working. It takes one file of about seventy lines. Everything comes from @react-wrapped/kit, the package with all the others in it.
What the file on this page renders. Three scenes, and the settings beside them.
1. Install
pnpm add @react-wrapped/kit gsapLoad the stylesheet once, where your app starts:
import "@react-wrapped/kit/styles.css";The frame is set in Inter. Installation says how to load it.
2. Say who it is about
The subject is the person the year belongs to. The handle and the caption sit in the top corner of every frame.
// Who the video is about. The handle and the caption sit in the corner of every frame.
const subject: WrappedSubject = {
handle: "ada",
name: "Ada",
avatarUrl: null,
caption: "Orbit Wrapped",
period: "Oct 2025 to Oct 2026",
};3. Bring your numbers
Scenes say what you hand them, so the numbers are yours to work out. Here they are made up: a count per day. Two of the scenes draw the year as a grid of cells, and calendarFromCounts turns the counts into that grid.
// Your numbers. Here: a made-up count per day from 11 October 2025, quiet at weekends.
const counts = Array.from({ length: 365 }, (_unused, day) => (day % 7 < 2 ? 0 : (day * 5) % 9));
const total = counts.reduce((sum, count) => sum + count, 0);
const activeDays = counts.filter((count) => count > 0).length;
// The year as a grid of cells: a level from 0 to 4 per day, the weekday it starts on and the month names.
const calendar = calendarFromCounts(counts, new Date("2025-10-11"));4. Place three scenes
A scene is a function. The first argument is what it says, the second is its key and its label on the timeline. The array is the video, in play order. These three are the opening, a big number and a list.
// A scene is a function: what it says, then the key and the label it goes by on the timeline.
const scenes = [
openingScene(
{ title: "My year on Orbit.", period: "@ada, Oct 2025 to Oct 2026", calendar, variant: 0 },
{ key: "opening", label: "Opening" },
),
bigNumberScene(
{
eyebrow: "In the last 12 months you made",
value: total,
title: ["contributions."],
lede: `On **${activeDays}** active days out of ${counts.length}.`,
calendar,
counter: "climb",
variant: 0,
},
{ key: "total", label: "Total contributions" },
),
listScene(
{
eyebrow: "Where it happened",
title: ["Your top", "repositories."],
items: [
{ name: "ada/orbit-cli", value: 742, unit: "commits", meta: "TypeScript", color: "#3178c6" },
{ name: "ada/telemetry", value: 355, unit: "commits", meta: "Go", color: "#00add8" },
{ name: "ada/dotfiles", value: 203, unit: "commits", meta: "Shell", color: "#89e051" },
],
},
{ key: "repositories", label: "Top repositories" },
),
];Stars in a text are marks: single stars put words in the accent colour, double stars make them bold.
5. Render WrappedView
WrappedView is the whole page: the player, its timeline and the settings. It needs the subject, the scenes, a seed and a config. The seed picks the transitions between scenes, so use something that is the person's own, such as their handle and the year. The config is everything a viewer can change, and it lives in your state.
export function FirstWrapped() {
// Everything a viewer can change (palette, format, speed, hidden scenes, edited texts) is this one object.
const [config, setConfig] = useState<WrappedConfig>(DEFAULT_WRAPPED_CONFIG);
return (
<WrappedView
subject={subject}
// The seed picks the transitions and the small variations. The same seed always plays the same video.
seed="ada:2026"
scenes={scenes}
config={config}
onConfigChange={setConfig}
/>
);
}That is a playing video. Hover the timeline to switch a scene off, move it, or open it and rewrite its headline. Change the palette and the format in the settings. All of it lands in config.
The whole file
import { useState } from "react";
import {
bigNumberScene,
calendarFromCounts,
DEFAULT_WRAPPED_CONFIG,
listScene,
openingScene,
type WrappedConfig,
type WrappedSubject,
WrappedView,
} from "@react-wrapped/kit";
// Who the video is about. The handle and the caption sit in the corner of every frame.
const subject: WrappedSubject = {
handle: "ada",
name: "Ada",
avatarUrl: null,
caption: "Orbit Wrapped",
period: "Oct 2025 to Oct 2026",
};
// Your numbers. Here: a made-up count per day from 11 October 2025, quiet at weekends.
const counts = Array.from({ length: 365 }, (_unused, day) => (day % 7 < 2 ? 0 : (day * 5) % 9));
const total = counts.reduce((sum, count) => sum + count, 0);
const activeDays = counts.filter((count) => count > 0).length;
// The year as a grid of cells: a level from 0 to 4 per day, the weekday it starts on and the month names.
const calendar = calendarFromCounts(counts, new Date("2025-10-11"));
// A scene is a function: what it says, then the key and the label it goes by on the timeline.
const scenes = [
openingScene(
{ title: "My year on Orbit.", period: "@ada, Oct 2025 to Oct 2026", calendar, variant: 0 },
{ key: "opening", label: "Opening" },
),
bigNumberScene(
{
eyebrow: "In the last 12 months you made",
value: total,
title: ["contributions."],
lede: `On **${activeDays}** active days out of ${counts.length}.`,
calendar,
counter: "climb",
variant: 0,
},
{ key: "total", label: "Total contributions" },
),
listScene(
{
eyebrow: "Where it happened",
title: ["Your top", "repositories."],
items: [
{ name: "ada/orbit-cli", value: 742, unit: "commits", meta: "TypeScript", color: "#3178c6" },
{ name: "ada/telemetry", value: 355, unit: "commits", meta: "Go", color: "#00add8" },
{ name: "ada/dotfiles", value: 203, unit: "commits", meta: "Shell", color: "#89e051" },
],
},
{ key: "repositories", label: "Top repositories" },
),
];
export function FirstWrapped() {
// Everything a viewer can change (palette, format, speed, hidden scenes, edited texts) is this one object.
const [config, setConfig] = useState<WrappedConfig>(DEFAULT_WRAPPED_CONFIG);
return (
<WrappedView
subject={subject}
// The seed picks the transitions and the small variations. The same seed always plays the same video.
seed="ada:2026"
scenes={scenes}
config={config}
onConfigChange={setConfig}
/>
);
}WrappedView props
| Prop | Type | What it does |
|---|---|---|
subject | WrappedSubject | Who the video is about. |
seed | string | Picks the transitions and the small variations. Give each person their own. |
scenes | readonly SceneInstance[] | The scenes in story order. |
config | WrappedConfig | Everything a viewer can change. |
onConfigChange | (next: WrappedConfig) => void | Called with the whole next config on every change. |
editable? | boolean | Whether this is the creator. Off, there are no settings, no editing and no scan: only the video. On unless you say otherwise. |
share? | { fileName: string; graphics?: readonly WrappedGraphic[]; onError?: (message: string) => void } | Adds the link and download buttons, and the gallery when there are graphics. |
opening? | { calendar; drops; found; onOpen; describeDay; actions?; sessionKey?; gate? } | Adds the scan and the scenes hidden in the year. |
viewer? | ReactNode | Shown under the video to someone who is not the creator, usually a way to make their own. |
autoplay? | boolean | Starts playing as soon as the video is built. On unless you say otherwise. |
playerRef? | Ref<WrappedPlayerHandle | null> | The player's handle: play, pause, seek. |
className? | string | A class for the page's root. |
classNames? | SlotClassNames<WrappedSlot> | A class per part, by slot name, for the page and everything on it: "view-player-panel", "player-play", "settings-chip". See styling. |
loading? | undefined | Not passed with a year. Pass it in place of the year and the view is the loading page. |
Where to go next
- More scenes. There are eleven, and the chart scene takes any of the twenty-one charts.
- A link that is the video. Keep the config in the URL and a shared link plays exactly what its owner set.
- Things to keep. Pass
sharefor the download button and the graphics. - Hidden scenes. Pass
openingand the scanner finds them. - Your own page. Build the page from parts when
WrappedViewis not the layout you want. - All of it at once. The full example is a whole year, directed from data.
