Convert stroked SVG into filled outlines. Every stroke becomes a filled shape,
overlapping shapes are merged with a boolean union, and the whole icon comes
out as a single <path> with no self-overlaps. That is what icon fonts, PDF
exporters, laser cutters and design tools that cannot render strokes need.
<path d="M3 13h4" stroke-width="2" stroke-linecap="round"/>
↓
<path d="M7.966 13.259L7.866 13.5 …Z" fill="currentColor"/>
Most existing converters offset each path segment separately and let the
renderer's nonzero fill rule hide the overlaps. The output looks right but
is made of dozens of overlapping subpaths: bigger files, font engines that
choke, and a mess in any vector editor. unstroke computes the true union,
so the output is exactly the visible outline and nothing else.
npx unstroke icons/ -o outlined/ # a whole folder, tree mirrored
npx unstroke icon.svg > icon-outline.svg # single file to stdout
npx unstroke icons/ -o out/ --optimize # plus SVGO (needs the svgo package)import { outlineSvg, outlinePathData } from 'unstroke';
const filled = outlineSvg(svgSource); // whole document
const thin = outlineSvg(svgSource, { strokeWidth: 1.5 }); // override the width
const d = outlinePathData('M3 13h4', { strokeWidth: 2, linecap: 'round' });Input that a filled path can't reproduce (dashed strokes, <text>, markers,
clip paths, opacity, several colours) is still converted,
with a warning on stderr or through the onWarning option. --strict and
strict: true turn those into failures instead.
The library has no Node dependencies and also runs in the browser; only the CLI and the SVGO pass need Node.
Everything else lives in the documentation at unstroke.vercel.app: every option, the lower-level API, what is supported, how it works and a comparison with other engines. The demo shows every test icon as source, outline and overlay.
pnpm install
pnpm test
pnpm build
The development page covers the repository layout, the fixture tests, the diff tools, how the docs and demo are built and how releases are cut with Changesets.
test/fixtures/ holds real SVGs: the hardest icons from a large production set (180° reversals,
micro segments, tight arcs, spirals, fills mixed with strokes, and every icon
that broke Skia, Paper.js or the previous webfont pipeline), icons from other
open source sets with different conventions (Feather, Lucide, Heroicons,
Iconoir; see test/fixtures/open-source/SOURCES.md) and hand-written files covering basic shapes, transforms, caps,
joins, fill rules, drawing direction, dots, self-intersections and
degenerate input. For each
one the test suite:
- converts it at stroke widths 0.5, 1, 1.5 and 2, writes each result to
test/__output__/stroke-<width>/<group>/<name>.svgand compares it with the committed version (pnpm vitest run -uaccepts changes), - rasterizes the original (with the same stroke width) and the outline and requires them to match within 0.05% of pixels.
Drop a new .svg into a fixtures folder to cover it; the output file is the
snapshot you review in a pull request.
pnpm docs starts the VitePress site (http://localhost:5173) with the
documentation and a demo page listing every icon from docs/icons/ and
test/fixtures/ as source, outline and an overlay of both. Icons are
converted from lib/ at build time and on every reload in dev. Vercel builds
the site from this repository, so every pull request gets a preview deployment
and every push to main updates unstroke.vercel.app.
scripts/pixel-diff.mts rasterizes originals and outlines for a whole folder
and reports the worst mismatches; scripts/diff-image.mts <file> renders one
icon side by side with a difference image.
unstroke is free to use, and its development is funded by sponsors. If it saves you time, consider becoming a sponsor on GitHub or donating on PayPal.
MIT. Uses clipper-lib (Boost Software License).