Tutorial
Step-by-step coding tutorial: typed code, terminal sessions and screenshots, one step per scene, timed to the typing.
Tutorial, defaults
Installation
npx shadcn add https://www.reelcn.dev/r/tutorial.jsonFiles land in src/reelcn/ with the items it builds on. Dependencies: zod.
Register the composition
<Composition
id="Tutorial"
component={Tutorial}
schema={tutorialSchema}
defaultProps={tutorialDefaults}
calculateMetadata={tutorialMetadata}
width={1920}
height={1080}
fps={30}
durationInFrames={1}
/>Render
npx remotion render Tutorial # 16:9
npx remotion render Tutorial --width=1080 --height=1920 # 9:16Props
PropTypeDescription
title*string
steps*{ kind: "code" | "screen" | "terminal"; content: string; language?: "ts" | "js" | "tsx" | "py" | "bash" | "json"; zoom?: boolean; }[]
theme"daylight" | "midnight" | "mono" | "ledger" | "afterglow" | "chromewave" | "neon" | "paper" | "sunset"Theme preset. Inherits the surrounding `ThemeProvider` when omitted.
brand{ accent?: string; logo?: string; font?: string; }
sfxbooleanPlays a soft whoosh as each scene starts.
musicstringBackground music file, looped under everything at 25% volume.
Built from
Use
- Getting-started guides and "how to" videos for developer tools
- Walking through a setup one command at a time
Avoid
- A single snippet. Use
code-block
Make it with an agent
With the reelcn agent skill installed, ask for the video in plain words, such as "make a launch video for Relay with reelcn". The skill picks a template, sets its props, reviews a contact sheet and renders.
Guides
Components take their colors from a theme, their timing from motion props and their layout from the canvas.
Source
registry/items/tutorial.tsx
/**
* @title Tutorial
* @category templates
* @description Step-by-step coding tutorial: typed code, terminal sessions and screenshots, one step per scene, timed to the typing.
* @duration data-driven
* @use Getting-started guides and "how to" videos for developer tools
* @use Walking through a setup one command at a time
* @avoid A single snippet. Use `code-block`
* @tags tutorial, code, terminal, developer, template
* @example
* <Composition
* id="Tutorial"
* component={Tutorial}
* schema={tutorialSchema}
* defaultProps={tutorialDefaults}
* calculateMetadata={tutorialMetadata}
* width={1920}
* height={1080}
* fps={30}
* durationInFrames={1}
* />
*/
import { z } from "zod";
import { LANGUAGES, type Scene, type Story } from "./story";
import { Storyboard, templateMetadata, templateSchema, templateStory } from "./storyboard";
export const tutorialSchema = templateSchema.extend({
title: z.string(),
steps: z
.array(
z.object({
kind: z.enum(["code", "terminal", "screen"]),
/** Code, a terminal session (lines starting "$ " are commands), or a screenshot URL. */
content: z.string(),
language: z.enum(LANGUAGES).optional(),
/** Slow zoom on a screenshot. */
zoom: z.boolean().optional(),
}),
)
.min(1),
});
export type TutorialProps = z.infer<typeof tutorialSchema>;
export const tutorialDefaults: TutorialProps = {
title: "Add a storyboard to your project",
steps: [
{ kind: "terminal", content: "$ npx shadcn add @reelcn/storyboard\nCreated src/reelcn/storyboard.tsx" },
{
kind: "code",
language: "tsx",
content:
'<Composition id="Storyboard" component={StoryVideo}\n schema={storySchema} calculateMetadata={storyMetadata}\n defaultProps={story} width={1920} height={1080}\n fps={30} durationInFrames={1} />',
},
{ kind: "terminal", content: "$ npx remotion render Storyboard out.mp4 --props=story.json\nRendered 294 frames" },
],
};
/** A terminal session as text: lines starting with "$ " are commands, the rest are output. */
function terminalLines(content: string) {
return content
.split("\n")
.map((line) =>
line.startsWith("$ ")
? { type: "command" as const, text: line.slice(2) }
: { type: "output" as const, text: line },
);
}
export function tutorialStory(props: TutorialProps): Story {
return templateStory(props, [
{ type: "title", kicker: "Tutorial", title: props.title, background: "grid" },
...props.steps.map((step, index): Scene => {
const label = `Step ${index + 1}`;
if (step.kind === "code") return { type: "code", code: step.content, language: step.language, title: label };
if (step.kind === "terminal") return { type: "terminal", lines: terminalLines(step.content), title: label };
return { type: "image", src: step.content, zoom: step.zoom, caption: label };
}),
]);
}
export function Tutorial(props: TutorialProps) {
return <Storyboard story={tutorialStory(props)} />;
}
export const tutorialMetadata = templateMetadata(tutorialStory);