reelcn
Templates

Templates

Data Story

Report video that walks through numbers: a title, then one bar, line, donut or stat scene per section with its caption.

Install

Usage

<Composition
  id="DataStory"
  component={DataStory}
  schema={dataStorySchema}
  defaultProps={dataStoryDefaults}
  calculateMetadata={dataStoryMetadata}
  width={1920}
  height={1080}
  fps={30}
  durationInFrames={1}
/>

Props

NameTypeDefaultDescription
titlestring
sections{ kind: "line" | "bar" | "donut" | "stat"; data: { label: string; value: number; }[]; caption: string; }[]
theme?"midnight" | "daylight" | "paper" | "neon" | "mono" | "sunset"Theme preset. Inherits the surrounding `ThemeProvider` when omitted.
brand?{ accent?: string; logo?: string; font?: string; }
sfx?booleanPlays a soft whoosh as each scene starts.
music?stringBackground music file, looped under everything at 25% volume.

Use

  • Quarterly results, year-in-review and research findings
  • Turning a dashboard into a narrated walkthrough

Avoid

Dependencies

zod

Source

registry/items/data-story.tsx
/**
 * @title Data Story
 * @category templates
 * @description Report video that walks through numbers: a title, then one bar, line, donut or stat scene per section with its caption.
 * @duration data-driven
 * @use Quarterly results, year-in-review and research findings
 * @use Turning a dashboard into a narrated walkthrough
 * @avoid A single number — use `stat-counter`
 * @tags data, charts, report, stats, template
 * @example
 * <Composition
 *   id="DataStory"
 *   component={DataStory}
 *   schema={dataStorySchema}
 *   defaultProps={dataStoryDefaults}
 *   calculateMetadata={dataStoryMetadata}
 *   width={1920}
 *   height={1080}
 *   fps={30}
 *   durationInFrames={1}
 * />
 */
import { z } from "zod";
import type { Scene, Story } from "./story";
import { Storyboard, templateMetadata, templateSchema, templateStory } from "./storyboard";

export const dataStorySchema = templateSchema.extend({
  title: z.string(),
  sections: z
    .array(
      z.object({
        kind: z.enum(["bar", "line", "donut", "stat"]),
        /** For `stat`, the first entry is the number and its label. */
        data: z.array(z.object({ label: z.string(), value: z.number() })).min(1),
        caption: z.string(),
      }),
    )
    .min(1),
});

export type DataStoryProps = z.infer<typeof dataStorySchema>;

export const dataStoryDefaults: DataStoryProps = {
  title: "How our users rendered in 2026",
  sections: [
    {
      kind: "stat",
      data: [{ label: "Videos rendered this year", value: 1284000 }],
      caption: "Up from 310,000 last year",
    },
    {
      kind: "bar",
      data: [
        { label: "Shorts", value: 58 },
        { label: "Tutorials", value: 21 },
        { label: "Launches", value: 13 },
        { label: "Podcasts", value: 8 },
      ],
      caption: "What people make, in percent",
    },
    {
      kind: "line",
      data: [
        { label: "Jan", value: 40 },
        { label: "Mar", value: 62 },
        { label: "May", value: 85 },
        { label: "Jul", value: 120 },
        { label: "Sep", value: 164 },
      ],
      caption: "Renders per day, in thousands",
    },
    {
      kind: "donut",
      data: [
        { label: "16:9", value: 44 },
        { label: "9:16", value: 41 },
        { label: "1:1", value: 15 },
      ],
      caption: "Formats",
    },
  ],
};

export function dataStoryStory(props: DataStoryProps): Story {
  return templateStory(props, [
    { type: "title", title: props.title, background: "grid" },
    ...props.sections.map(
      (section): Scene =>
        section.kind === "stat"
          ? { type: "stat", label: section.data[0].label, value: section.data[0].value, caption: section.caption }
          : { type: "chart", kind: section.kind, title: section.caption, data: section.data },
    ),
  ]);
}

export function DataStory(props: DataStoryProps) {
  return <Storyboard story={dataStoryStory(props)} />;
}

export const dataStoryMetadata = templateMetadata(dataStoryStory);

Related