Redraw is currently in technical preview, available to start-react-native.dev subscribers. API is unstable.
Stroke Width Presets
Three ready-made width functions for the most common animated stroke
patterns. Pass any of them to brush.addStroke(color, widthPreset):
import { SingleStrokeBrush, TaperedTip } from "redraw";
const stroke = new TaperedTip({ baseWidth: 25, headRatio: 2, progress: 0 });
const brush = new SingleStrokeBrush();
brush.addStroke(color, stroke);
// Animate every frame:
stroke.props.progress = animation(ctx, { duration: 12, boomerang: true });
The presets are RenderFunction subclasses, built from hand-written WGSL,
no unplugin-typegpu required. They live in
packages/redraw/src/nodes/brushes/StrokeWidthPresets.ts.
TaperedTip
A fat leading "head" that tapers behind itself, retracting to the base
width as progress reaches 1. Pairs naturally with path.segment(0, progress) for a stylus-like draw-on stroke.
new TaperedTip({
baseWidth: 25, // resting stroke width (px)
headRatio: 2, // head is 2× the base when fully extended
taperStart: 0.5, // taper begins halfway along the path
progress: 1, // 0..1; 0 = no head, 1 = head retracted
});
Props (default in parens):
| Prop | Default | Effect |
|---|---|---|
baseWidth | 25 | Width when no head is present. |
headRatio | 2 | Multiplier for the head width. |
taperStart | 0.5 | Fraction of ctx.t where the taper begins. |
progress | 1 | Drives the head: 0 = full head, 1 = retracted. |
CalligraphyWidth
Width modulates with the angle between the path tangent and a fixed pen orientation. Mimics a flat nib: thick on the broadside, thin along the pen's axis.
new CalligraphyWidth({
minWidth: 5,
maxWidth: 30,
penAngle: 0.7, // radians
});
Props:
| Prop | Default | Effect |
|---|---|---|
minWidth | 5 | Width when path runs along the pen axis. |
maxWidth | 30 | Width when path runs perpendicular to the pen. |
penAngle | 0.7 | Pen orientation in radians. Animate for variation. |
PulseWidth
Single-frequency sine modulation along the path. Animate phase to make
it travel; raise frequency for tighter ripples.
new PulseWidth({
baseWidth: 25,
amplitude: 6,
frequency: 6,
phase: 0, // animate this each frame: ctx.time * speed
});
Props:
| Prop | Default | Effect |
|---|---|---|
baseWidth | 25 | Resting width. |
amplitude | 6 | Peak deviation above and below baseWidth. |
frequency | 6 | Cycles across the path's arc length. |
phase | 0 | Phase offset; animate to make ripples travel. |
const pulse = new PulseWidth({});
brush.addStroke(color, pulse);
// every frame:
pulse.props.phase = ctx.time * 4;
The renderer holds a regression snapshot for PulseWidth; the gallery
doesn't have a live demo yet, but the test snapshot illustrates the look:

When to write your own
The three presets cover the common cases. If you need:
- Width that responds to
ctx.sdf(radial taper, distance-from-edge) - Combined modulation (taper + pulse + calligraphy)
- Width as a function of
ctx.pos(spatial effects)
…write your own. See Custom Effects: Stroke.