Skip to content

SVG Authoring & Troubleshooting ​

Each SVG becomes one single-color glyph in the font. Most icons exported from a design tool work as they are. This page explains what changes when an SVG becomes a glyph, and how to fix the surprises.

Every comparison below is live: the docs site builds real fonts from the SVGs shown, using vite-svg-2-webfont. In each card, the white chip on the left is the SVG as a browser draws it, and the glyph on the right is what the generated font renders. The dashed outline is the glyph's box.

These rules come from the engine, so they apply to every package: the Vite plugin, Node.js, the browser, Rust, and the CLI.

What works automatically ​

You don't need to prepare these. The engine converts them as drawn:

  • Strokes are converted to filled outlines, including line caps and joins.
  • Transforms on shapes and groups are applied.
  • Hidden elements (display="none") are skipped.
  • Basic shapes (rect, circle, ellipse, polygon, line, polyline) work like paths.
  • fill-rule="evenodd" holes are kept.
  • Light shapes drawn on top of a dark shape, such as a white play triangle on a circle, become holes, which is usually what you want. The next section explains why.

Converted as drawn

icon-hidden-layer
icon-play
icon-shapes
icon-stroke
icon-transform

Colors are ignored; nested shapes become holes ​

A glyph has a single color: wherever there is a shape, the text color shows. The engine ignores fill colors and decides what is filled by nesting:

  • A shape drawn inside another shape becomes a hole.
  • A shape inside that hole is filled again, and so on, alternating with each level.
  • Shapes that only partly overlap merge into one.

This turns white details into cutouts, but it causes two common surprises:

  • Background rectangles invert the icon. A white square behind the artwork counts as a shape, so the artwork becomes a hole in a solid square.
  • Colored details become holes. A blue dot on a black badge is nested inside the badge, so it is cut out.

As exported

icon-badge
icon-warning

Background removed; colorGlyphs: ['badge']

icon-badge
icon-warning

Fix:

  • Delete background rectangles and any other shape that exists only to fill the artboard.
  • If a detail should stay solid, merge it into the shape around it (for example with "Union" in your design tool) instead of stacking it on top.
  • If the icon really needs two colors, enable colorGlyphs for it. Color glyphs require TTF, WOFF, or WOFF2 output, and some platforms show the monochrome version instead.

Clip paths, masks, and opacity are ignored ​

The engine reads the shapes themselves. It doesn't apply clip-path or mask, and it ignores opacity. Clipped or masked artwork appears in full, and a faded shape is as solid as the rest.

clip-path, mask, opacity

icon-donut
icon-half-moon
icon-shadow

Flattened to plain shapes

icon-donut
icon-half-moon
icon-shadow

Fix: flatten the effect into real geometry before exporting:

  • Figma: select the layers and use Flatten (and Outline stroke if needed).
  • Illustrator: use Object › Expand and Object › Clipping Mask › Release, then combine the shapes with the Pathfinder.
  • Inkscape: use Path › Object to Path, then Path › Intersection or Difference.

Remove faded shapes or draw them as separate icons. With colorGlyphs, solid fills with fill-opacity keep their opacity.

Convert text to outlines ​

<text> elements are dropped, so an icon made only of text comes out empty. Convert text to outlines (paths) in your design tool before exporting.

<text>

A
icon-letter

Converted to outlines

icon-letter

Keep artboards consistent ​

A glyph's size comes from the SVG's viewBox, not from the drawing inside it. An icon drawn small in the middle of its artboard stays small in the font, with the empty space around it.

Different padding

icon-dot
icon-square

Same padding

icon-dot
icon-square

Fix: draw every icon on the same artboard size (for example 24×24) with the same padding around the artwork, and export the full artboard.

Mixed sizes and alignment ​

When icons have different viewBox sizes or sit at different heights, these options control how they line up. The comparisons show the same four icons next to text: a square, a low bar, a 16×16 square, and a wide arrow.

  • normalize (on by default) scales every icon to the same height, so a 16×16 icon matches 24×24 ones. When it is off, icons keep their size relative to the tallest viewBox.
  • centerVertically centers each glyph's shape vertically, ignoring where it sat on its artboard.
  • fixedWidth gives every glyph the width of the widest one. The dashed boxes show each glyph's width.

Defaults

Save now

normalize: false

Save now

normalize: false, centerVertically: true

Save now

fixedWidth: true

Save now

To move every icon up or down relative to the text baseline, adjust descent. A positive value moves glyphs down.

Jagged curves outside the Vite plugin ​

The engine stores each glyph on a grid of whole units. By default the grid is as tall as the tallest icon's viewBox, so a set of 24×24 icons gets only 24 units, and curves come out visibly faceted.

fontHeight: 24 (generator default here)

icon-ring
icon-target

fontHeight: 1000 (plugin default)

icon-ring
icon-target

Fix: the Vite plugin already uses fontHeight: 1000. With the Node.js, Rust, or CLI interfaces, set fontHeight to 1000 or more.

File names become class names ​

Each file name, without .svg, becomes the icon's name and CSS class. File-based interfaces use the name as it is, so characters that mean something in CSS break the class:

  • arrow.left.svg produces the selector .icon-arrow.left, which needs two classes, icon-arrow and left.
  • my icon.svg produces .icon-my icon, which matches an icon element inside .icon-my.

arrow.left.svg, my icon.svg

icon-arrow.left
icon-my icon

arrow-left.svg, my-icon.svg

icon-arrow-left
icon-my-icon

Fix: name files with letters, digits, hyphens, and underscores only. Class names are case-sensitive, so Add.svg and add.svg produce different classes. With the Node.js API, a rename callback can turn paths into safe names. The browser and in-memory Rust APIs reject unsafe names.

Pin codepoints for stable output ​

Each icon gets a Unicode codepoint, assigned in order starting at U+F101. Adding an icon can shift the codepoints of the icons after it. That doesn't matter while CSS and font are always deployed together, but it breaks anything that stores codepoints separately: hand-written content: "\f102" rules, cached CSS served with a new font, or exports to design tools.

The comparison renders the raw codepoints U+F101–U+F103 in three fonts. After bell.svg is added, U+F102 changes from remove to bell, unless codepoints are pinned.

add, remove

U+F101
U+F102
U+F103

add, bell, remove

U+F101
U+F102
U+F103

add, bell, remove; codepoints pinned

U+F101
U+F102
U+F103

Fix: pin the codepoints of icons you publish with codepoints. New icons continue from the start codepoint, skipping pinned values.

vite.config.ts
ts
viteSvgToWebfont({
    context: './src/icons',
    codepoints: { add: 0xf101, remove: 0xf102 },
});

Troubleshooting checklist ​

SymptomLikely causeFix
Icon is a solid square with the artwork cut outA background rectangleRemove backgrounds
A detail is a hole instead of a solid shapeThe detail is nested inside another shapeMerge it, or use colorGlyphs
Parts that should be hidden or faded are visibleclip-path, mask, or opacityFlatten the effect
Icon is emptyText, or every shape is hiddenConvert text to outlines
Icons look different sizesDifferent artboard padding or viewBox sizesKeep artboards consistent, normalize
Icons sit too high or low next to textArtwork placement on the artboardcenterVertically or descent
Curves look facetedLow fontHeight outside the pluginSet fontHeight
A class has no effectThe file name contains ., spaces, or other symbolsRename the file
Wrong icon after adding new onesCodepoints shiftedPin codepoints
No icon at all, or a placeholder boxThe CSS isn't loaded, or the base class is missingImport the virtual stylesheet and use both classes: icon icon-add

Released under the MIT License.