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.

tsx
// 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.

tsx
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

PropTypeDefault
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

    Default72

  • swapAtnumber

    Frame at which the caret starts expanding into the block; the caret itself appears seven frames earlier

    Default20

  • typeSpeednumber

    Frames for the first typed character; every next gap shrinks by a fixed ratio so the typing accelerates toward the end

    Default1

  • 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

    Default400

  • classNamestring

    Optional className passed to the underlying span