Skip to content

unstrokeStroked SVG in, filled outlines out.

Every stroke becomes a filled shape, the overlaps are merged with a real boolean union, and the whole icon comes out as one path with nothing overlapping itself.

A stroked drop icon converted into a filled outlineA stroked drop icon converted into a filled outline

One icon, before and after

This is armchair, a 24 px icon from the test set. It goes in as 4 stroked shapes and comes out as one filled <path> of 1300 bytes, or 1091 after SVGO. The conversion took 21.9 ms.

source: strokes
<svg
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M5 11a2 2 0 0 1 2 2v2h10v-2a2 2 0 1 1 4 0v4a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-4a2 2 0 0 1 2 -2" />
  <path d="M5 11v-5a3 3 0 0 1 3 -3h8a3 3 0 0 1 3 3v5" />
  <path d="M6 19v2" />
  <path d="M18 19v2" />
</svg>
result: a single filled path
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
  <path d="M20 10.174C21.226 10.676 21.96 11.66 22 13V17C21.947 18.763 20.724 19.896 19 20C19 20.352 19.096 21.102 18.866 21.5C18.43 22.255 17.26 22.10 …"/>
</svg>

The problem, in one picture

A stroke gets covered with small polygons, one per segment, one per join and one per cap. Most converters stop right there and let the renderer's fill rule hide the overlaps. unstroke merges them into the actual outline before it writes anything out.

194 pieces before the union. Every darker patch is an overlap. This is what overlapping subpaths look like when you open the file in a vector editor.
3 subpaths, 30 curves after the union and the curve fit. There are no overlaps left and nothing to clean up.
Overlay of the original stroke in blue over the result drawn in grey. A grey fringe on either side would mean a mismatch. There isn't one.

Get started

bash
npm install unstroke
bash
pnpm add unstroke
bash
yarn add unstroke

You can convert a folder from the command line or a string from code. Both take the same options.

bash
npx unstroke icons/ -o outlined/                 # a whole folder, tree mirrored
npx unstroke icons/ -o thin/ --stroke-width 1.5  # every weight from one source
npx unstroke icons/ -o out/ --optimize           # plus SVGO
ts
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' });

The getting started guide walks through both.

How it compares

We ran 203 icons from a production icon set through every stroke-to-outline engine we could script, passed each result through the same SVGO step, and rasterized it against the original.

engineavg bytesmean pixel mismatchwrong / failed
unstroke10610.000%0
Skia PathOps (CanvasKit)17210.000%0
Paper.js booleans16100.000%0
FontForge5890.244%17

FontForge wins on size only because it's imprecise. Skia and Paper.js are accurate on this sample, but they keep every fragment their intersections produce. On the full set of 5130 icons, both curve-based engines silently break icons that unstroke handles. The full comparison and the reasoning behind polygons go into the details.

What people use it for

Icon fonts, first of all. Glyphs have no concept of a stroke, and font engines either reject overlapping contours or render them wrong. unstroke gives you one clean set of contours per icon.

PDF export, plotters and laser cutters. These treat every path as a fill, or they need the actual outline to cut along.

Design tools that don't support strokes. Some import paths but ignore stroke-width, and some render strokes differently than a browser does.

And one source for several weights. You keep the icons as strokes and generate the 1 px, 1.5 px and 2 px variants at build time with --stroke-width.

Sponsors

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.

Sponsors