Ink Underline

A hand-dragged ink stroke that draws itself across in a few stop-motion poses

Installation

$ pnpm dlx shadcn@latest add @remocn/ink-underline

Usage

The stroke renders in normal flow — a block of width by thickness + 4 — so it stacks under text in a flex column. Pair it with handwrite and start the drag the moment the writing finishes:

// src/Root.tsx
import { AbsoluteFill, Composition } from "remotion";
import { Handwrite, handwriteDuration } from "@/components/remocn/handwrite";
import { InkUnderline } from "@/components/remocn/ink-underline";
 
const url = "remocn.dev";
 
const InkUnderlineScene = () => (
  <AbsoluteFill
    style={{
      background: "#f1eee7",
      alignItems: "center",
      justifyContent: "center",
      flexDirection: "column",
      gap: 4,
    }}
  >
    <div style={{ position: "relative", width: 420, height: 96 }}>
      <Handwrite text={url} fontSize={64} />
    </div>
    <InkUnderline width={420} delay={handwriteDuration(url)} />
  </AbsoluteFill>
);
 
export const RemotionRoot = () => (
  <Composition
    id="InkUnderline"
    component={InkUnderlineScene}
    durationInFrames={60}
    fps={30}
    width={1280}
    height={720}
  />
);

Handwrite centres itself inside its nearest positioned ancestor, which is why it sits in a sized position: relative box here. The underline needs no such box — it is a block in the flow.

The wobble in the stroke comes from seed: the same seed always draws the same curve, and changing it gives a different hand. durationSteps is how many stop-motion poses the drag takes, so the real length in frames is durationSteps * step.

Props

PropTypeDefaultDescription
widthrequired
numberStroke length in pixels
color
string"#6f7f35"Ink colour (any valid CSS color)
thickness
number9Brush width in pixels at full pressure
pressure
number1Weight the brush lands on, as a fraction of thickness
release
number0.15Weight the brush lifts off at; below pressure it thins into a dry tail
grain
number1Edge roughness; breaks the thin tail into dry-brush flecks, 0 gives clean edges
delay
number0Frames on the local clock before the drag begins
durationSteps
number5Stop-motion poses the drag takes
seed
string"ink"Seed for the curve wobble — same seed, same stroke
step
number3Frames per stop-motion pose — the tempo control