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-layericon-playicon-shapesicon-strokeicon-transformColors 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-badgeicon-warningBackground removed; colorGlyphs: ['badge']
icon-badgeicon-warningFix:
- 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
colorGlyphsfor 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-donuticon-half-moonicon-shadowFlattened to plain shapes
icon-donuticon-half-moonicon-shadowFix: 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>
icon-letterConverted to outlines
icon-letterKeep 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-doticon-squareSame padding
icon-doticon-squareFix: 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 tallestviewBox.centerVerticallycenters each glyph's shape vertically, ignoring where it sat on its artboard.fixedWidthgives 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-ringicon-targetfontHeight: 1000 (plugin default)
icon-ringicon-targetFix: 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.svgproduces the selector.icon-arrow.left, which needs two classes,icon-arrowandleft.my icon.svgproduces.icon-my icon, which matches aniconelement inside.icon-my.
arrow.left.svg, my icon.svg
icon-arrow.lefticon-my iconarrow-left.svg, my-icon.svg
icon-arrow-lefticon-my-iconFix: 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+F101U+F102U+F103add, bell, remove
U+F101U+F102U+F103add, bell, remove; codepoints pinned
U+F101U+F102U+F103Fix: pin the codepoints of icons you publish with codepoints. New icons continue from the start codepoint, skipping pinned values.
viteSvgToWebfont({
context: './src/icons',
codepoints: { add: 0xf101, remove: 0xf102 },
});Troubleshooting checklist
| Symptom | Likely cause | Fix |
|---|---|---|
| Icon is a solid square with the artwork cut out | A background rectangle | Remove backgrounds |
| A detail is a hole instead of a solid shape | The detail is nested inside another shape | Merge it, or use colorGlyphs |
| Parts that should be hidden or faded are visible | clip-path, mask, or opacity | Flatten the effect |
| Icon is empty | Text, or every shape is hidden | Convert text to outlines |
| Icons look different sizes | Different artboard padding or viewBox sizes | Keep artboards consistent, normalize |
| Icons sit too high or low next to text | Artwork placement on the artboard | centerVertically or descent |
| Curves look faceted | Low fontHeight outside the plugin | Set fontHeight |
| A class has no effect | The file name contains ., spaces, or other symbols | Rename the file |
| Wrong icon after adding new ones | Codepoints shifted | Pin codepoints |
| No icon at all, or a placeholder box | The CSS isn't loaded, or the base class is missing | Import the virtual stylesheet and use both classes: icon icon-add |