Video components

Install components from the registry to copy their source into your project:

pnpm odori add component \
  @odori/stage \
  @odori/title-reveal \
  @odori/code-proof \
  @odori/terminal \
  @odori/browser-demo \
  @odori/connection-story \
  @odori/end-card

pnpm odori catalog components prints the catalog with each component's aspect ratios, minimum duration, and reduced-motion behavior. Registry dependencies install with the component that needs them.

Use the component catalog to preview components and inspect their props, timing, and source.

  • videos/
    • components/
      • stage/
        • stage.tsx
        • stage.preview.tsx
      • title-reveal/
        • title-reveal.tsx
        • title-reveal.preview.tsx
      • code-proof/
        • code-proof.tsx
        • code-proof.preview.tsx
      • terminal/
        • terminal.tsx
        • terminal.preview.tsx
      • end-card/
        • end-card.tsx
        • end-card.preview.tsx

Use the components as ordinary React:

<Scene id="proof" duration="8s">
  <Stage grid={false}>
    <CodeProof
      code={'export const preview = "instant";'}
      focus={[1]}
      language="tsx"
      title="videos/launch/video.tsx"
    />
  </Stage>
</Scene>

Temporal contracts

Video components need more than prop types. Registry metadata describes:

  • supported aspect ratios
  • recommended and minimum duration
  • natural entrance and exit frames
  • content limits, such as maximum headline length
  • reduced-motion behavior
  • required fonts and audio

odori test and the registry test suite check these contracts, while the copied implementation stays fully editable.

Scale to the video format

Components multiply their design values by useDesignScale(), which measures the shorter side of the frame against a 1080px reference. The same component reads correctly at 1920x1080, 1080x1920, and 1080x1080 without a separate vertical variant.

const scale = useDesignScale();
<div style={{fontSize: 116 * scale, letterSpacing: "-0.045em"}}>{title}</div>;

Component development in Studio

The component source stays independent from its development fixture. A sibling *.preview.tsx file defines representative props, controls, duration, canvas size, and edge cases for Studio. The fixture is similar to a Storybook story with a deterministic frame clock.

Organize components

Studio groups components by category paths, such as Interface/Controls. It resolves categories in this order:

Directory path. A component at videos/components/interface/controls/combobox/ belongs to Interface/Controls.

Category configuration. Add category.json to customize a directory's display name and order:

videos/components/product-ui/category.json
{"name": "Product UI", "order": 1}

order is the only way to sequence your own families; without one they follow the ones the registry ships, alphabetically.

Preview metadata. A fixture can declare its category when the directory doesn't supply one. Registry components use this to retain their category after installation:

export default defineComponentPreview({
  title: "Combobox",
  category: "Interface/Controls",
  // ...
});

Use fixture metadata for components distributed outside your project.

Reuse application components

Videos can import deterministic components from an application's root components/ directory. Keep those components under their existing product ownership instead of duplicating them for video.

Add an adapter under videos/components/product/ when the application component expects routing, live data, or browser interaction. The adapter supplies frozen data and disables behavior that depends on wall-clock or application state.

Initial component library

FamilyComponents
TypographyTitle reveal, metric callout, captions
Developer proofTerminal, code proof
InterfaceBrowser demo
NarrativeConnection story
BrandStage, end card

Install the agent quickstart

Install the quickstart skill to let your agent set up a project and choose components:

npx skills add allenzhou101/odori \
  --skill odori-quickstart \
  --yes

Where components come from

odori add fetches and caches the published registry. Its output lists the registry URL and installed files:

$ pnpm odori add component cursor-focus
registry: https://odori.dev/r/v1
  create  videos/components/cursor-focus/cursor-focus.tsx
  create  videos/components/cursor-focus/cursor-focus.preview.tsx
ok @odori/cursor-focus to videos/components/cursor-focus/

Use --dry-run to list files without writing them. Downloads are verified against published hashes. Offline, the command uses the cache or the registry bundled with the CLI.

Point registryUrl in odori.config.ts at a fork or a pinned version, or set ODORI_REGISTRY for one command.

Installing with the shadcn CLI

The registry is shadcn-compatible, so any project can take the source:

npx shadcn@latest add https://odori.dev/r/v1/title-reveal.json

The shadcn CLI copies component source but doesn't register audio cues in your brand. Use odori add for automatic cue registration, or add the mapping yourself.

Three kinds of entry

The registry supports three entry types:

KindWhat it isWhat odori add does
ComponentReact that renders framesCopies source into videos/components/<name>/
CueSynthesis that renders samplesCopies source, and registers the cue in your brand
AssetA music or sound fileFetches the file into public/, verifies it against the published hash, and registers the cue by URL

Use odori add component <asset> --force to replace an installed audio file. Asset files aren't handled by odori diff, odori update, or the shadcn installer.

Source ownership

Commit odori.lock.json to track installed versions. The CLI uses it to distinguish local edits from upstream changes:

odori diff                 # what changed, in your project and upstream
odori diff terminal --full # the diff itself
odori update               # apply upstream changes to untouched components

Updates are opt-in and source-aware. A component that is both edited locally and changed upstream is reported as diverged and left alone until you pass --force.

On this page