Most "handwritten" text on the web is just a script font. It looks fine, but it's static,
and every "a" is identical to every other "a". Real handwriting isn't like that.
Check it here
So I built glyphed.js: a small library that renders text, icons, annotations and
charts as hand-drawn SVG that draws itself when it scrolls into view.
One line to use it
npm install glyphed.js
import { write } from "glyphed.js";
write("#hello", "Hello, world!");
That's it. The letters draw in, stroke by stroke, when #hello enters the viewport.
It's not a font
Every character is a real monoline SVG path on a 24-unit grid. Most glyphs ship with
~3 hand-drawn variants, and two layers of randomness make it read as handwriting:
- Variant picking. Each render picks a variant at random, but never the same one used last time for that character, so "hello" never has two identical l's in a row.
- Per-letter jitter. Every letter gets a small random rotation (±7°), baseline drift and scale (±10%), pivoted around its own center so the word stays aligned.
The same word never renders the same way twice. That's the point.
How the draw-in works
Each letter is a <path> with pathLength="1", stroke-dasharray: 1 and
stroke-dashoffset: 1, so it's fully hidden behind its own dash gap. Setting the offset
to 0 with a staggered CSS transition draws it left to right, letter by letter.
One gotcha I hit: a zero-length dash still paints its round cap, so every undrawn stroke
showed up as a dot. The fix is to start strokes with visibility: hidden and flip them
visible at the exact moment their draw begins.
No animation library, no requestAnimationFrame loop: just CSS transitions and an
IntersectionObserver.
Same pen, more things
Once you have a pen, you can draw anything with it.
Icons: 68 of them, on the same grid with the same wobble, and tree-shakable:
import { drawIcon } from "glyphed.js";
import { rocket } from "glyphed.js/icons";
drawIcon("#logo", rocket, { size: 48, label: "Launch" });
Annotations: underline, circle, box, highlight, strike, cross and bracket on any element:
annotate("#price", { type: "circle", color: "#c63d24" });
Charts: bar, line, pie and donut with hatched fills and handwritten labels, plus
sparklines. The palette is colorblind-checked, and hatch direction alternates as a
second cue.
drawChart("#stats", {
type: "bar",
data: [{ label: "Mon", value: 12 }, { label: "Tue", value: 19 }],
title: "Commits per weekday",
});
The details
- Zero dependencies, ESM, strict TypeScript
-
renderText()returns a plain HTML string, so it works for SSR - One CSS variable for color:
--hw-color - Text stays copyable and accessible
-
{ animate: false }gives static output
Where it fits
Portfolio heroes, landing pages, blog posts, slide decks, dashboards that shouldn't look
like dashboards: anywhere you want the "sketched on a napkin" feel without shipping images.
⭐ GitHub: https://github.com/a-elhaag/glyphed.js
📦 npm: https://www.npmjs.com/package/glyphed.js
I'd love feedback, especially on glyph shapes you think look off. What would you build with it?
Top comments (1)