Caret Swap

A gradient caret expands into a block that consumes the first text, then types the second

Installation

$ pnpm dlx shadcn@latest add @remocn/caret-swap

Usage

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.

Props

PropTypeDefaultDescription
fromTextrequired
stringThe phrase shown first and consumed by the caret block
toTextrequired
stringThe phrase typed out after the swap
fontSize
number72Font size in pixels; caret and block scale with it
swapAt
number20Frame at which the caret starts expanding into the block; the caret itself appears seven frames earlier
typeSpeed
number1Frames 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
number400CSS font-weight
className
stringOptional className passed to the underlying span