snapcn
28 components
DocsComponentsTemplatesVideo EditorNewShowcaseMarketplaceRoadmapChangelog

Getting started

IntroductionInstallationAgent skill
GitHub

Star snapcn

Free, open-source components.

Production-ready Remotion animations, transitions and backgrounds — you own the code.

Star on GitHubXFollow

MIT licensed · own your code.

© 2026 snapcn

Loading
snapcn
28 components
DocsComponentsTemplatesVideo EditorNewShowcaseMarketplaceRoadmapChangelog

Getting started

IntroductionInstallationAgent skill
GitHub

Star snapcn

Free, open-source components.

Production-ready Remotion animations, transitions and backgrounds — you own the code.

Star on GitHubXFollow

MIT licensed · own your code.

© 2026 snapcn

DocsComponentsTemplatesVideo EditorNewShowcaseMarketplaceRoadmapChangelog
Getting startedIntroductionInstallationAgent skill

Prompt Send

A composer that unrolls from a hairline, writes a brief while the camera rides the caret, then cuts back out as a pointer arrives and sends it

Installation

pnpm
$ pnpm dlx shadcn@latest add @snapcn/prompt-send

Component code

The exact file shadcn add copies into your project.

Usage

// src/Root.tsx
import { Composition } from "remotion";
import { PromptSend } from "@/components/snap-cn/prompt-send";

const BriefScene = () => (
  <PromptSend
    text="Add a text reveal, a soft blur transition, and a gradient background"
    placeholder="Describe the scene you want…"
    chips={["Add a text reveal", "Try a blur transition", "Pick a background"]}
  />
);

export const RemotionRoot = () => (
  <Composition
    id="PromptSend"
    component={BriefScene}
    durationInFrames={165}
    fps={30}
    width={1280}
    height={720}
  />
);

Give it a longer sentence, more chips, a shorter field — it all still lands:

<PromptSend
  width={420}
  fieldHeight={52}
  radius={12}
  chips={[]}
  text="A one-line control, and copy long enough that the field has to scroll under its own caret"
/>

The typing is eased, and that is the whole shot

The surprise in the reference recording is that it does not type at a constant rate. It accelerates in and decelerates out: about 17 characters a second over the first word, 33 through the middle, and a visible settle on the last three. Fitting an ease-in-out sine to the character count read off twenty-four frames lands within 0.30 characters rms across all 69 — no constant rate comes close.

That matters beyond the typing, because the camera rides the same curve. Pin a camera to a character count and it lurches once per keystroke; pin it to the continuous curve underneath and it glides, while the caret hops forward and back onto its mark — which is exactly the ±8px wobble the reference shows.

typeStart and typeDuration are the two props. Everything about the rate falls out of them, and any length of copy finishes in the same window.

Two cuts, not two zooms

At 1.975s the view cuts to 2.327× in a single frame, landing with the caret 57% across the frame, and then tracks left with the sentence. At 4.008s it cuts again, out to 1.44×, centred on the send button — which is where the pointer is already heading.

Both are steps. cameraFor returns one of exactly three states and there is a test asserting that no frame of the clip sits anywhere else. Ramping a cut, even across two frames, turns it into a zoom and loses the snap.

Both marks are fractions of the frame, never pixel offsets, so a wider panel or a bigger type size reframes instead of drifting off the edge.

The unroll

The panel does not fade in and it does not scale up from nothing. A 2px hairline appears at its top edge and the box grows downward out of it — top edge pinned, bottom edge travelling its full height in 445ms — while the width goes 0.93 → 1. The writing surface and the chip strip are two heights on the same curve with different durations, which is why the strip surfaces from under the field late instead of being there from the first frame.

measured
unroll445ms, cubic-bezier(0.3, 0, 0.35, 1)
placeholder + button417ms, over 300ms, linear
chipsone every 100ms, each rising 11.4px
typing0.915s → 3.94s, ease-in-out sine
cut in1.975s, 2.327×, caret at (0.573, 0.488)
cut out4.008s, 1.44×, button at (0.531, 0.526)
pointerenters 4.04s, halves its distance every 44ms
click4.658s

Nothing is a constant tuned to one sentence

Every offset above is either a fixed inset — a padding, which should not change when the type does — or a ratio of a font size, which must. layoutFor turns four numbers into the whole geometry, and the defaults are the recording's own measurements, so the shipped shot is pixel-identical to the one that was measured:

  • Longer copy scrolls the field under the caret, the way a real single-line input does, instead of running past its edge. It scrolls on the continuous curve, so the line glides while the caret hops.
  • More chips wrap, and the panel grows by exactly the rows they needed. The stagger keeps running in label order across them.
  • No chips at all and the panel hugs the field.
  • A short fieldHeight puts the text and the send button on the same row, and the line yields the button's gutter — but only then, so the reference's tall field still gets its full width.

The pointer belongs to the screen

It is drawn outside the camera. A pointer is a property of the display, not of the thing on it, so it must not grow when the shot pushes in — and the reference's does not. Set cursor={false} to drop it, and the click and the send bloom go with it.

The caret blinks off the last keystroke

Not off a wall clock. A real caret stays solid while you type and starts blinking a fixed interval after you stop, and anchoring it that way is what gets the reference's two measured blinks — dark at 4.267s, back at 4.741s — without a single extra number.

Colour and wording

The proportions and the timing are measured. The colours and the labels are not — the reference is a specific product, and its blue and its copy are its brand. The paint is the shadcn token set, the field takes its surface from the Input primitive's own style context so the two cannot drift, and the backdrop wash is layered from accentColor (which defaults to theme.primary), so it is the installer's accent.

Props

PropTypeDefaultDescription
text
string"Add a text reveal, a soft blur transition, and a gradient background"The brief that types itself. Any length — the field scrolls under the caret rather than clipping
placeholder
string"Describe the scene you want…"Shown until the first character lands, and set one caret width right of the text origin, as the reference has it
chips
string[] | stringfour snapcn suggestionsThe suggestion row. Accepts an array or the comma-separated string the customizer sends. They wrap and the panel grows; pass [] for a bare field
sendIcon
ReactNodean up arrowReplaces the glyph inside the send button
width
number636.2Panel width in stage px. The stage is 890×486 and is scaled to fill the composition
fieldHeight
number121.2Height of the writing surface. The panel's height is derived from it, the chips and its own padding
fontSize
number14Prompt type size. Line box, caret width and the field's text inset are all ratios of it
chipFontSize
number12.3Chip label size. Chip height, padding and the row gap are all ratios of it
radius
number22Panel corner radius. The field's is this less 2, as the reference's is
sendSize
number27.4Diameter of the send button
fontFamily
stringInterOnly useful if you have already loaded a face — a Remotion bundle has no CSS variables to fall back on
revealAt
number0.025Seconds at which the panel starts to unroll
revealDuration
number0.445How long the unroll takes. The writing surface finishes at 0.83 of it
fieldAt
number0.417Seconds at which the placeholder and the send button fade up, over 300ms
caretAt
number0.742Seconds at which the caret arrives. It does not fade — it is whole on one frame
chipsAt
number0.545Seconds at which the first chip starts to rise
chipStagger
number0.1Seconds between chips, in label order across however many rows there are
typeStart
number0.915Seconds at which typing begins
typeDuration
number3.025Seconds the whole sentence takes, on an ease-in-out sine
cutInAt
number1.975Seconds at which the view cuts in to the caret. A step, never a ramp
cutOutAt
number4.008Seconds at which it cuts back out to the send button
zoomIn
number2.327Scale of the first cut, measured off the reference
zoomOut
number1.44Scale of the second cut
focusX
number0.573Where the caret sits across the frame while the camera rides it, as a fraction
focusY
number0.4881Where the caret sits down the frame, as a fraction
outX
number0.5305Where the send button sits across the frame after the second cut
outY
number0.5261Where the send button sits down the frame after the second cut
cursorAt
number4.042Seconds at which the pointer enters from the bottom-right corner
cursorDuration
number0.42How long its approach takes. It halves its distance every 44ms, not a constant glide
clickAt
number4.658Seconds at which it presses the button. The send bloom opens 220ms later
cursor
booleantrueShow the pointer at all. Off drops the click and the send bloom with it
accentColor
stringtheme.primaryThe wash behind the panel and the send bloom, layered as alpha over theme.background
theme
Partial<SnapCnTheme>—Token overrides. The field's surface comes from the Input primitive's own style context, so the two cannot drift
mode
"light" | "dark""light"Which token set to resolve against
speed
number1Multiplies the frame clock. Every beat above scales with it

The recording is 5.02s and ends 140ms into the send bloom. The opening of that bloom is measured; the sweep that follows it is the component's own, which is why the default duration is 165 frames rather than 151.