Paper Wobble
Per-pose reshoot jitter that makes any content feel hand-placed on a desk
Installation
$ pnpm dlx shadcn@latest add @remocn/paper-wobbleUsage
Wrap anything. The wrapper holds one offset for a whole stop-motion pose, then snaps to the next — the wobble of a photo that was picked up and put back down.
// src/Root.tsx
import { AbsoluteFill, Composition } from "remotion";
import { PaperWobble } from "@/components/remocn/paper-wobble";
const PaperWobbleScene = () => (
<AbsoluteFill
style={{
background: "#f1eee7",
alignItems: "center",
justifyContent: "center",
flexDirection: "column",
gap: 40,
}}
>
<PaperWobble seed="title">
<h1 style={{ fontSize: 64, color: "#26242c" }}>Shot on a desk</h1>
</PaperWobble>
<PaperWobble seed="card">
<YourCard />
</PaperWobble>
</AbsoluteFill>
);
export const RemotionRoot = () => (
<Composition
id="PaperWobble"
component={PaperWobbleScene}
durationInFrames={90}
fps={30}
width={1280}
height={720}
/>
);Give each wrapped block its own seed — two instances sharing a seed move in
lockstep, which reads as one rigid sheet rather than two objects on a desk.
Components that already carry their own jitter, like paper-sticker, do not
need this wrapper — doubling it up just doubles the shake.
The wrapper is display: inline-block and nothing else: no size, no
background, no positioning. If you pass a transform through style, it is
applied first and the jitter is appended, so your own positioning survives.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
childrenrequired | React.ReactNode | — | The content to wobble |
seed | string | "wobble" | Seed for the jitter — same seed, same motion |
amp | number | 1.4 | Maximum offset in pixels on each axis |
rotAmp | number | 0.35 | Maximum rotation in degrees |
step | number | 3 | Frames per stop-motion pose — the tempo control |
className | string | — | Passed through to the wrapper div |
style | React.CSSProperties | — | Merged over the defaults; a transform here is composed before the jitter |