Svg draw
Draws any set of SVG paths on stroke by stroke, staggered, with an optional fill fade once each finishes.
SvgDraw, check
Installation
npx shadcn add https://www.reelcn.dev/r/svg-draw.jsonFiles land in src/reelcn/ with the items it builds on.
Usage
<Center>
<SvgDraw viewBox="0 0 24 24" paths={["M4 12h16", "M4 6h10", "M4 18h7"]} size={220} />
</Center>Props
PropTypeDescription
delaynumberFrames to wait before entering.
durationnumberEnter duration in frames. Defaults to 0.6s. Some items reuse
duration for an unrelated local timing. See that item's own prop docs.exitnumber | booleanExit at the end of the parent `<Sequence>`.
false keeps it on screen, a number sets the exit length in frames.motionMotionPersonalityOverride the theme's motion personality.
posterbooleanRenders fully entered, with no exit: for a still frame or thumbnail. Defaults to
false.holdFramesnumberDelays the start of the exit window by this many frames, for a minimum on-screen hold. Defaults to
0.paths*string[]One or more
d attributes, drawn in order.viewBox*stringMust be space- or comma-separated: `"minX minY width height"`.
colorstringStroke color. Defaults to the theme foreground.
strokeWidthnumberIn `viewBox` units, not design units, it scales with the drawing, like an icon's stroke. Default
3.stepnumberFrames between one path's draw and the next. Defaults to 0.15s.
sizenumberRendered width in design units. Height follows the `viewBox` ratio. Default
200.fillstringFades a fill in behind each path once it finishes drawing. Omit for a stroke-only line drawing.
styleCSSProperties
classNamestring
Built from
Use
- A logo mark, icon or custom illustration drawing itself on screen
- Sketching a diagram or shape that a `callout` or label then points at
Avoid
- A single hand-drawn circle or arrow. Use
scribble-circleorarrow
Guides
Components take their colors from a theme, their timing from motion props and their layout from the canvas.
Source
registry/items/svg-draw.tsx
/**
* @title Svg Draw
* @category product
* @description Draws any set of SVG paths on stroke by stroke, staggered, with an optional fill fade once each finishes.
* @duration data-driven
* @use A logo mark, icon or custom illustration drawing itself on screen
* @use Sketching a diagram or shape that a `callout` or label then points at
* @avoid A single hand-drawn circle or arrow. Use `scribble-circle` or `arrow`
* @tags svg, draw, path, icon, illustration, line art
* @example
* <Center>
* <SvgDraw viewBox="0 0 24 24" paths={["M4 12h16", "M4 6h10", "M4 18h7"]} size={220} />
* </Center>
*/
import type React from "react";
import { type MotionProps, StrokeOverlay, tween, useMotion, useTheme, useViewport } from "./core";
export type SvgDrawProps = MotionProps & {
/** One or more `d` attributes, drawn in order. */
paths: string[];
/** Must be space- or comma-separated: `"minX minY width height"`. */
viewBox: string;
/** Stroke color. Defaults to the theme foreground. */
color?: string;
/** In `viewBox` units, not design units, it scales with the drawing, like an icon's stroke. */
strokeWidth?: number;
/** Frames between one path's draw and the next. Defaults to 0.15s. */
step?: number;
/** Rendered width in design units. Height follows the `viewBox` ratio. */
size?: number;
/** Fades a fill in behind each path once it finishes drawing. Omit for a stroke-only line drawing. */
fill?: string;
style?: React.CSSProperties;
className?: string;
};
const clamp01 = (value: number) => Math.min(Math.max(value, 0), 1);
export function SvgDraw({
paths,
viewBox,
color,
strokeWidth = 3,
step,
size = 200,
fill,
style,
className,
...motion
}: SvgDrawProps) {
const theme = useTheme();
const { u } = useViewport();
const m = useMotion(motion);
const stroke = color ?? theme.colors.foreground;
const frames = step ?? Math.round(m.fps * 0.15);
const box = viewBox.split(/[\s,]+/).map(Number);
const vbWidth = box[2];
const vbHeight = box[3];
const width = u(size);
const height = vbWidth > 0 ? width * (vbHeight / vbWidth) : width;
return (
<svg
aria-hidden="true"
className={className}
width={width}
height={height}
viewBox={viewBox}
style={{ opacity: 1 - m.exit, overflow: "visible", ...style }}
>
{paths.map((d, index) => {
const drawn = clamp01(
tween(m.frame, m.fps, { from: m.delay + index * frames, duration: m.enterFrames, motion: m.preset }),
);
const filled = fill
? clamp01(
tween(m.frame, m.fps, {
from: m.delay + index * frames + m.enterFrames,
duration: Math.round(m.enterFrames * 0.6),
motion: "smooth",
}),
)
: 0;
return (
<g key={index} opacity={drawn > 0 ? 1 : 0}>
{fill ? <path d={d} fill={fill} fillOpacity={filled} stroke="none" /> : null}
<StrokeOverlay
d={d}
kind={theme.stroke}
seed={`svg-draw-${index}`}
color={stroke}
strokeWidth={strokeWidth}
drawn={drawn}
extraProps={{ strokeLinecap: "round", strokeLinejoin: "round" }}
/>
</g>
);
})}
</svg>
);
}