Caret Swap
A gradient caret expands into a block that consumes the first text, then types the second
$ pnpm dlx shadcn@latest add @remocn/caret-swapUsage
The first text sits still until swapAt. Seven frames earlier the caret pops in after the last character; at swapAt it swells leftward into a full block with a sharp acceleration, consuming the text right to left in six frames. The block then dissolves its gradient tail to the right — the left edge holds still and what remains is the caret line at the start of the row. After a beat it types the second text — typeSpeed frames for the first character with every following gap shrinking, one accelerando to the finish — growing rightward from the anchor while drifting left to end centered. After the last character the caret blinks. All timings measured frame-by-frame from the reference footage.
// src/Root.tsx
import { Composition } from "remotion";
import { CaretSwap } from "@/components/remocn/caret-swap";
const CaretSwapScene = () => (
<CaretSwap
fromText="Executing it?"
toText="That's where everyone quits"
/>
);
export const RemotionRoot = () => (
<Composition
id="CaretSwap"
component={CaretSwapScene}
durationInFrames={100}
fps={30}
width={1280}
height={720}
/>
);With Backdrop
The component renders transparent — supply the background via Backdrop. It ships single-theme; edit the copied file to re-theme colors.
import { Composition } from "remotion";
import { CaretSwap } from "@/components/remocn/caret-swap";
import { Backdrop } from "@/components/remocn/backdrop";
const CaretSwapScene = () => (
<Backdrop fill={{ type: "color", value: "#fdf6f0" }}>
<CaretSwap
fromText="Executing it?"
toText="That's where everyone quits"
/>
</Backdrop>
);
export const RemotionRoot = () => (
<Composition
id="CaretSwap"
component={CaretSwapScene}
durationInFrames={100}
fps={30}
width={1280}
height={720}
/>
);Syncing
The exported caretSwapLength(toText, swapAt, typeSpeed) helper returns the frame on which the last character lands — use it as the lower bound of the Sequence, plus whatever hold you want for the blinking caret.
Props10
| Prop | Type | Default |
|---|---|---|
| fromTextrequired | string— The phrase shown first and consumed by the caret block | |
| toTextrequired | string— The phrase typed out after the swap | |
| fontSize | number72 Font size in pixels; caret and block scale with it | |
| swapAt | number20 Frame at which the caret starts expanding into the block; the caret itself appears seven frames earlier | |
| typeSpeed | number1 Frames for the first typed character; every next gap shrinks by a fixed ratio so the typing accelerates toward the end | |
| color | string"#171717" Text color (any valid CSS color) | |
| caretColor | string"#FF4DAA" Top color of the caret gradient | |
| caretColorEnd | string"#8A5CF6" Bottom color of the caret gradient | |
| fontWeight | number400 CSS font-weight | |
| className | string— Optional className passed to the underlying span | |
- fromTextrequiredstring
The phrase shown first and consumed by the caret block
- toTextrequiredstring
The phrase typed out after the swap
- fontSizenumber
Font size in pixels; caret and block scale with it
Default
72 - swapAtnumber
Frame at which the caret starts expanding into the block; the caret itself appears seven frames earlier
Default
20 - typeSpeednumber
Frames for the first typed character; every next gap shrinks by a fixed ratio so the typing accelerates toward the end
Default
1 - colorstring
Text color (any valid CSS color)
Default
"#171717" - caretColorstring
Top color of the caret gradient
Default
"#FF4DAA" - caretColorEndstring
Bottom color of the caret gradient
Default
"#8A5CF6" - fontWeightnumber
CSS font-weight
Default
400 - classNamestring
Optional className passed to the underlying span