Polaroid

An instant-photo frame with a handwritten caption for video, images, or live compositions

Installation
$ pnpm dlx shadcn@latest add @remocn/polaroid

Usage

The media is children, so anything can play inside the window. Give your media width: 100% and height: 100% — the frame sizes the window, the objectFit is yours.

tsx
// src/Root.tsx
import { AbsoluteFill, Composition, OffthreadVideo, staticFile } from "remotion";
import { Polaroid } from "@/components/remocn/polaroid";
 
const PolaroidScene = () => (
  <AbsoluteFill
    style={{
      background: "#f1eee7",
      alignItems: "center",
      justifyContent: "center",
    }}
  >
    <Polaroid caption="first light">
      <OffthreadVideo
        src={staticFile("clip.mp4")}
        muted
        style={{ width: "100%", height: "100%", objectFit: "cover" }}
      />
    </Polaroid>
  </AbsoluteFill>
);
 
export const RemotionRoot = () => (
  <Composition
    id="Polaroid"
    component={PolaroidScene}
    durationInFrames={150}
    fps={30}
    width={1280}
    height={720}
  />
);

A living photograph

The window takes a whole composition, not just a file. The scene inside keeps running on the real 30fps clock while the frame around it moves in stop-motion poses — a photograph that never stopped playing, with no pre-rendered video.

tsx
<Polaroid caption="the demo">
  <YourProductScene />
</Polaroid>

Stopping the photo

Wrap the media in Remotion's <Freeze> to make the photograph stop playing — the usual reason is that the card is being flipped aside and should read as a still print again.

tsx
import { Freeze } from "remotion";
 
const FREEZE_AT = 90;
 
<Polaroid caption="the demo">
  <Freeze frame={FREEZE_AT} active={frame >= FREEZE_AT}>
    <YourProductScene />
  </Freeze>
</Polaroid>;

The card has no jitter or positioning of its own. Wrap it in paper-wobble to put it on the desk, and position it yourself.

Every measurement derives from width, so the card keeps the proportions of the original 652px print at any size. captionSize defaults to the same scale; pass it explicitly only when you want the caption off that ratio.

Props8

PropTypeDefault
childrenrequired
React.ReactNode—

The media inside the window; give it width and height of 100%

caption
string—

Handwritten caption; omit it and the band stays empty

captionAt
number0

Local frame the caption starts writing

width
number652

Card width; every other measurement scales from it

frameColor
string"#fdfcf8"

Card stock colour

captionColor
string"#26242c"

Ink colour of the caption

captionSize
numberscales from width

Caption font size; defaults to 36 at the default width

step
number3

Frames per stop-motion pose, forwarded to the caption

  • childrenrequiredReact.ReactNode

    The media inside the window; give it width and height of 100%

  • captionstring

    Handwritten caption; omit it and the band stays empty

  • captionAtnumber

    Local frame the caption starts writing

    Default0

  • widthnumber

    Card width; every other measurement scales from it

    Default652

  • frameColorstring

    Card stock colour

    Default"#fdfcf8"

  • captionColorstring

    Ink colour of the caption

    Default"#26242c"

  • captionSizenumber

    Caption font size; defaults to 36 at the default width

    Defaultscales from width

  • stepnumber

    Frames per stop-motion pose, forwarded to the caption

    Default3