Ink Underline
A hand-dragged ink stroke that draws itself across in a few stop-motion poses
$ pnpm dlx shadcn@latest add @remocn/ink-underlineUsage
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.
Props10
| Prop | Type | Default |
|---|---|---|
| widthrequired | number— Stroke length in pixels | |
| color | string"#6f7f35" Ink colour (any valid CSS color) | |
| thickness | number9 Brush width in pixels at full pressure | |
| pressure | number1 Weight the brush lands on, as a fraction of thickness | |
| release | number0.15 Weight the brush lifts off at; below pressure it thins into a dry tail | |
| grain | number1 Edge roughness; breaks the thin tail into dry-brush flecks, 0 gives clean edges | |
| delay | number0 Frames on the local clock before the drag begins | |
| durationSteps | number5 Stop-motion poses the drag takes | |
| seed | string"ink" Seed for the curve wobble — same seed, same stroke | |
| step | number3 Frames per stop-motion pose — the tempo control | |
- widthrequirednumber
Stroke length in pixels
- colorstring
Ink colour (any valid CSS color)
Default
"#6f7f35" - thicknessnumber
Brush width in pixels at full pressure
Default
9 - pressurenumber
Weight the brush lands on, as a fraction of thickness
Default
1 - releasenumber
Weight the brush lifts off at; below pressure it thins into a dry tail
Default
0.15 - grainnumber
Edge roughness; breaks the thin tail into dry-brush flecks, 0 gives clean edges
Default
1 - delaynumber
Frames on the local clock before the drag begins
Default
0 - durationStepsnumber
Stop-motion poses the drag takes
Default
5 - seedstring
Seed for the curve wobble — same seed, same stroke
Default
"ink" - stepnumber
Frames per stop-motion pose — the tempo control
Default
3