Skip to content
Axialis Developer Network

Guide

Cleaning ugly SVGs before you ship

Remove editor cruft, flatten transforms, fix precision, and make SVGs currentColor-ready before they land in your codebase or CI pipeline.

Updated

  • #svg
  • #optimization
  • #svgo
  • #currentcolor
  • #icon-pipeline
  • #vector
  • #cleanup

Every icon starts its life as a designer's export. By the time an SVG leaves Figma, Illustrator, or Sketch and lands in your pull request, it can carry dozens of kilobytes of editor metadata, hundreds of invisible layers, transform stacks that no browser ever asked for, and hardcoded fill colors that will never respect your design system's theme. This guide walks through exactly what that cruft is, why it matters, and how to remove it reliably — manually, with tooling, or as part of an automated pipeline.

What "editor cruft" actually looks like

Open any freshly exported SVG from a major design tool and you will find at least some of the following:

  • <sodipodi:...> and <inkscape:...> namespace elements (Inkscape-specific metadata)
  • <dc:creator>, <cc:Work>, <rdf:RDF> RDF/Dublin Core metadata blocks
  • Adobe-specific xmp:CreatorTool and xmp:ModifyDate attributes embedded in <metadata>
  • Sketch sketch:type and Figma data-name attributes on groups and layers
  • Dozens of id="path1234" attributes on every element, generated to satisfy the tool's internal graph — useless in production
  • Empty <g> wrapper elements that group nothing
  • Invisible elements with display:none or opacity:0 that the designer switched off but never deleted
  • The full document title in a <title> element that will be read aloud by screen readers if you inline the SVG without overriding it

None of these survive the browser render in a way you can see, but they inflate file sizes, pollute the DOM, interfere with CSS targeting, and cause confusion when other developers read the source.

A before/after to make this concrete

A real-world icon exported from Figma (simplified for readability):

<!-- BEFORE: Figma export, 1.4 KB -->
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
  xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink">
  <title>Settings</title>
  <metadata>
    <rdf:RDF xmlns:dc="http://purl.org/dc/elements/1.1/">
      <cc:Work><dc:creator>Figma</dc:creator></cc:Work>
    </rdf:RDF>
  </metadata>
  <g id="Icons/Settings" data-name="Icons/Settings">
    <g id="Frame" opacity="1">
      <rect id="Bounding-Box" width="24" height="24" fill="transparent" display="none"/>
      <path id="path-1" d="M12 15a3 3 0 100-6 3 3 0 000 6z"
        fill="#1A1A1A" fill-rule="evenodd"/>
      <path id="path-2" transform="translate(0,0) rotate(0) scale(1)"
        d="M19.14 12.94..." fill="#1A1A1A"/>
    </g>
  </g>
</svg>

<!-- AFTER: cleaned, 290 bytes -->
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
  <path d="M12 15a3 3 0 100-6 3 3 0 000 6z" fill="currentColor" fill-rule="evenodd"/>
  <path d="M19.14 12.94..." fill="currentColor"/>
</svg>

That is an 80% size reduction. More importantly, the cleaned version is now themeable.

Flattening transforms into path data

Design tools often build icons through composition — a shape positioned via a group transform, then rotated, then scaled. When exported, those transforms survive as literal transform="translate(4,4) rotate(45) scale(0.5)" attributes. Browsers handle this correctly, but it creates three problems:

  1. Combining paths in CSS (e.g. with clip-path or mask) requires coordinates to be in the same space.
  2. Any post-processing tool that reads path coordinates (a QA check, a sprite builder, a bounds calculator) will get wrong numbers unless it also evaluates the transform chain.
  3. When you read the path data yourself to understand an icon's shape, you cannot visualize it without mentally applying the transforms.

Flattening means applying each transform to the d attribute of the path itself, then removing the transform attribute. The path data changes; the visual output is identical.

Most SVG optimizers do this automatically. In SVGO, the convertTransform plugin handles it. If you are applying transforms manually (rare but sometimes needed for complex rotation chains), use a library like svgpath to apply the matrix operations to the coordinate data.

Watch out: transform on a <g> with multiple children

When a transform sits on a <g> that wraps several paths, flattening requires applying the same matrix to each child path individually, then removing the <g> (or keeping it if it serves a semantic grouping purpose). Automated tools handle this, but if you are reviewing SVGs by hand, it is easy to miss a group-level transform.

Decimal precision

Figma and Illustrator export path coordinates to six or more decimal places by default: M 12.000000 4.499823. The visual difference between 12.000000 and 12 is zero for icon-sized artwork (anything under 512px). Excess decimal places are dead weight.

A safe rule for UI icons is two decimal places — enough to preserve sub-pixel precision at standard icon sizes while keeping path strings short. At 1px = 1 SVG unit and a 24×24 viewBox, two decimal places gives you precision to 0.01 pixels, which is meaningless at the sizes icons are actually displayed.

SVGO's cleanupNumericValues plugin applies this automatically. If you are writing your own cleaner, use a regex that rounds values in d attributes: this is a common but fiddly operation to get right around arc commands (A/a), so use a tested library rather than a hand-rolled regex.

Removing hidden layers and unused IDs

Hidden layers

The cleanup rule is simple: if an element has display:none, visibility:hidden, or opacity:0 and is not referenced by a <use>, <clipPath>, or <mask>, delete it. It contributes nothing to the output.

The "referenced" caveat is important. A shape can be invisible itself but still be used as a clip mask for a visible element. Check whether any id on the hidden element is referenced elsewhere before removing it.

Unused IDs

Every id in an inlined SVG pollutes the document's global ID namespace. If you inline two copies of the same icon (a pattern that appears constantly in component-based UIs), identical IDs create a validity error and can break CSS selectors and aria references.

The right approach:

  • Remove IDs that exist only because the export tool generated them (id="path1234", id="Group_47").
  • Keep IDs that your CSS or JavaScript actually reference.
  • In a component system (React, Vue, etc.), auto-generate unique IDs per instance for any <clipPath> or <filter> that the SVG uses internally. Every major SVG component library handles this; if yours does not, it is a bug.

Making SVGs currentColor-ready

currentColor is the mechanism that lets an icon inherit its color from its surrounding text context — meaning a single SVG file works at any color, in any theme, without duplication. It is the baseline expectation for any icon intended for use in a design system.

The change is simple: replace every hardcoded color value on fill and stroke attributes with currentColor:

<!-- Before -->
<path fill="#111827" .../>
<path stroke="#374151" stroke-width="2" .../>

<!-- After -->
<path fill="currentColor" .../>
<path stroke="currentColor" stroke-width="2" .../>

Also check style attributes — some tools emit inline styles (style="fill:#111827") instead of presentation attributes. Both need to be converted.

Dual-tone icons

Some icons intentionally use two colors — a foreground shape and a secondary accent. In that case, a single currentColor is not enough. The conventional pattern is to use currentColor for the primary color and a --icon-accent CSS custom property for the secondary:

<path fill="currentColor" .../>
<path fill="var(--icon-accent, currentColor)" .../>

The currentColor fallback in var() means the icon degrades gracefully if the property is not set.

For more on how currentColor interacts with dark-mode variants and contrast requirements, see the guide on themeable icons and dark-mode variants.

The cleanup checklist

Check What to do Automated?
Editor metadata (<metadata>, RDF, XMP) Remove entirely Yes (SVGO removeMetadata)
Tool-specific namespaces (inkscape:, sodipodi:, sketch:) Remove namespace and all child elements Yes (SVGO removeXMLNS, removeEditorsNSData)
Invisible elements Remove if not referenced Yes (SVGO removeHiddenElems)
Identity transforms (translate(0,0), scale(1)) Remove Yes (SVGO cleanupAttrs, convertTransform)
Non-identity transforms Flatten into path data Yes (SVGO convertTransform)
Unused IDs Remove Partly (SVGO cleanupIds; keep referenced IDs)
Hardcoded fill/stroke colors Replace with currentColor Partly (SVGO convertColors + manual review)
Excess decimal places Round to 2 dp Yes (SVGO cleanupNumericValues)
Empty <g> elements Remove Yes (SVGO collapseGroups, removeEmptyContainers)
<title> for decorative icons Remove or make empty + aria-hidden="true" Manual
Duplicate path data Merge with <use> or deduplicate Manual / SVGO mergePaths

Automated vs manual cleanup

When automation is enough

For a large icon library (hundreds of icons), manual review of every file is not realistic. A well-configured SVGO pass will catch everything in the checklist above automatically. Run it as part of your build pipeline so no raw exports ever land in the repository.

A minimal svgo.config.js for icon libraries:

// svgo.config.js
module.exports = {
  plugins: [
    {
      name: 'preset-default',
      params: {
        overrides: {
          // Never remove viewBox — it controls scaling behavior
          removeViewBox: false,
          // Inline styles can be needed for multi-color icons; review case by case
          inlineStyles: false,
        },
      },
    },
    'removeMetadata',
    'removeEditorsNSData',
    {
      name: 'convertColors',
      params: {
        currentColor: true, // replaces any color matching the icon's body color
      },
    },
    {
      name: 'cleanupNumericValues',
      params: { floatPrecision: 2 },
    },
  ],
};

Run it with: npx svgo --config svgo.config.js --folder src/icons --output dist/icons

When you need a human eye

Automation cannot make judgment calls. You still need a person to:

  • Confirm that currentColor was applied to the intended paths and not to accent elements that should stay a fixed color.
  • Spot icons where flattening transforms caused a path to land outside the viewBox (this happens with certain rotation/translation combinations and can result in clipped output).
  • Catch icons where detail was lost because two nearly-overlapping paths were merged by mergePaths when they should have remained separate.
  • Validate that the cleaned icon still looks correct at small sizes (16px and below), where the relationship between strokes and fills matters more than it does at 48px.

A practical workflow is to run automated cleanup on every file, then do a visual QA pass on the output — ideally using a grid view that shows the icon at 16px, 24px, and 48px side by side.

Integrating cleanup into CI

The cleanest approach is to treat raw SVG exports as source files that are never committed to the main branch directly. Instead:

  1. Designers commit raw exports to a src/icons/raw/ directory.
  2. A CI step (GitHub Actions, GitLab CI, etc.) runs SVGO against src/icons/raw/ and writes optimized output to dist/icons/.
  3. A second CI step runs your visual QA checks (pixel-diff against approved references, or a contrast/sizing lint).
  4. Only the dist/icons/ output is deployed or published to your npm package.

This keeps the build reproducible and ensures no unreviewed raw export ever ships. It also makes it easy to upgrade your SVGO configuration and regenerate the entire library in one pass.

For information on what happens after cleanup — specifically exporting your cleaned icons to framework-specific component formats — see the guide on exporting icons to React, Vue, SwiftUI, and WPF.