Skip to content
react-wrapped

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

bash
pnpm add @react-wrapped/kit gsap

Load the stylesheet once, where your app starts:

tsx
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.

tsx
// 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.

tsx
// 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.

tsx
// 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.

tsx
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

first-wrapped.tsx
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

PropTypeWhat it does
subjectWrappedSubjectWho the video is about.
seedstringPicks the transitions and the small variations. Give each person their own.
scenesreadonly SceneInstance[]The scenes in story order.
configWrappedConfigEverything a viewer can change.
onConfigChange(next: WrappedConfig) => voidCalled with the whole next config on every change.
editable?booleanWhether 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?ReactNodeShown under the video to someone who is not the creator, usually a way to make their own.
autoplay?booleanStarts 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?stringA 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?undefinedNot passed with a year. Pass it in place of the year and the view is the loading page.

Where to go next

Last updated . Read this page as Markdown.