threadtext

Text,
embroidered.

npm ↗
GitHub ↗
TypeScriptZero dependenciesReact + Vanilla JS

Give it a word in any loaded font and it renders as raised satin floss on a transparent ground — threads that run across each stroke and fan around the curves, lifted into 3D and sewn in one satin stitch at a time. Drop it over any background. Photorealistic by construction, from the font’s real glyph geometry — not a raster filter, not an AI image.

Live demo — type a word

Size80%
Weight680
Sew rate140/s
Sew style
Stitch
Colour
Sheen
Backstitch

Click the artwork and type — the word re-fits to the width as you go, and colour, font, and size change live without re-stitching. Hit Replay sew-in to watch it embroider itself one satin row at a time.

How it works

Glyphs drive the flow

The word is rasterised, then an exact distance transform gives a flow field — smoothed in double-angle orientation space so opposite edge-normals reinforce. Threads run across each stroke and fan cleanly around the curves, with no moiré.

Thread, lit in 3D

Thousands of pre-shaded thread sprites are laid along the flow and lifted into 3D by a dome-shade normal map, so each strand catches the light along its length. The floss sits on a transparent ground — drop it over any background — and a cursor-following sheen turns the threads over as you move.

Sewn in, one row at a time

A breadth-first pass over the stitch graph reveals the word one satin cross-row at a time — the needle following each stroke, letters filling left to right. The sew-in plays on load and whenever you hit Replay; typing and every live control re-fit and redraw the word instantly.

Accessibility & motion

The canvas is decorative; the word is exposed to assistive tech via role="img" and an aria-label. prefers-reduced-motion: reduce is honoured — the sew-in is skipped and the word is drawn instantly.

Usage

Drop-in component

import { ThreadText } from '@liiift-studio/threadtext'

<ThreadText text="Thread" font='"Your Font", serif' weight={680} />

Hook

import { useThreadText } from '@liiift-studio/threadtext'

const ref = useThreadText({ text: 'Thread', font: '"Your Font", serif', weight: 680 })
<div ref={ref} />

Vanilla JS

import { createThreadText } from '@liiift-studio/threadtext'

// Load the face first (any @font-face / next/font / CSS Font Loading API), then:
const thread = createThreadText(document.getElementById('host'), {
  text: 'Thread', font: '"Your Font", serif', weight: 680,
})

thread.setText('Threads') // appended letters sew in; unrelated text re-sews
thread.replay()           // re-run the full sew-in
thread.destroy()          // cancel rAF, remove listeners, free canvases

Options

ThreadText options reference
OptionDefaultDescription
textThe word (or short phrase) to embroider. Required.
font'Georgia, serif'CSS font-family of an already-loaded font. The glyph geometry drives the stitch flow — load it however you like before calling.
weight680Numeric font weight (100–900). Drives the wght axis via the standard font shorthand.
axesVariable-font axes, e.g. { opsz: 40, SOFT: 60 }. Applied via the canvas fontVariationSettings API (Chrome/Edge/Safari); ignored where unsupported. Use for opsz and custom axes.
threadColor'#fffbf3'Floss colour — the lit crest of each thread. Hex or rgb(). Changes live, no re-stitch.
fill0.9Fraction of the container width the word spans — its size. The word re-fits to width (with the remainder as padding) on load and resize.
pitchautoThread spacing in px. Smaller = finer, denser stitching (and more work). Derived from the fitted height if unset.
sewStyle'machine''machine' — satin cross-rows fill each stroke in parallel. 'hand' — one letter at a time (left to right), entering at the widest part near the top and working down, thin edges last, like hand embroidery.
stitchMode'satin'The stitch texture: 'satin' (smooth parallel floss), 'cross' (little X's), 'chain' (looped links), or 'running' (short dashes).
sewRate110Cross-rows (machine) or stitches (hand) laid per second during the sew-in.
sheentrueCursor-following radial sheen on the overlay canvas.
animatetruePlay the sew-in on mount and on replay(). When false (or reduced-motion), the word is drawn instantly.
editablefalseMake the surface typeable — adds a real (transparent) input over the artwork, so clicking focuses it, the soft keyboard opens on touch, and typing/paste/IME all work. Enter replays. Shows a caret; pair with onTextChange.
onTextChangeCallback fired with the new text whenever the user edits it in editable mode.
reducedMotionautoForce reduced-motion. Auto-detected from prefers-reduced-motion if omitted.

no-code

Use it in Webflow & Framer

The same effect, no build step — drop it straight into your design tool.

Webflow

One script tag, then mark any element with data-threadtext. Configure it with data-* attributes.

<!-- Site SettingsCustom CodeFooter, or an Embed element -->
<script src="https://cdn.jsdelivr.net/npm/@liiift-studio/threadtext/dist/threadtext.webflow.min.js"></script>

<!-- Then add data-threadtext to any text element -->
<h1 data-threadtext>Your headline</h1>

Framer

Insert → Code → New Component, then paste ThreadText.tsx ↗. It imports the core from esm.sh and exposes every option in the property panel — no build step.

import { /* core */ } from "https://esm.sh/@liiift-studio/threadtext"