How big should an icon SVG be?
Typical file sizes for simple and complex logo SVGs, what makes an icon file bloated, and how to set and enforce a size budget for an icon library.
5 min read
A clean icon SVG is small: often under a kilobyte, rarely more than a few. When a logo file is 40 KB or 300 KB, something other than the drawing is in there. Knowing the normal ranges lets you spot those files quickly, and a simple budget keeps them from creeping back into your library.
Typical sizes
These are typical ranges for optimized files, before server compression, based on what kind of artwork the logo contains:
| Kind of logo | Typical size |
|---|---|
| Single-color mark built from a few shapes | 0.3 to 1.5 KB |
| Flat multi-color logo | 1 to 4 KB |
| Logo with gradients, masks or many overlapping shapes | 3 to 10 KB |
| Detailed mark or a wordmark outlined from text | 5 to 20 KB |
| Anything above 20 KB | Worth investigating |
| Anything above 50 KB | Almost always a problem |
The drawing's complexity drives the size, not its dimensions. A coin with a diamond on it costs the same few hundred bytes at 16 px and at 1,024 px.
Over the network, servers usually compress SVG with gzip or Brotli. Markup with repeated tags and attribute names shrinks a lot; dense path data, which is mostly unique numbers, shrinks less. Measure both raw and compressed sizes, because the compressed number is what users download and the raw number is what the browser has to parse.
Measuring
From a terminal:
# Raw size in bytes
wc -c icons/coin.svg
# Approximate size over the wire with gzip
gzip -9 -c icons/coin.svg | wc -c
# The 20 largest SVGs in a folder (the last line is the total)
find icons -name '*.svg' -exec wc -c {} + | sort -n | tail -21
Sort by size first. In a large library, a handful of outliers usually account for most of the excess, and they tend to share one of the causes below.
What bloats an SVG
1. An embedded bitmap. The most dramatic case. The file looks like an SVG but contains an <image> element with a base64-encoded PNG or JPEG inside it, often tens or hundreds of kilobytes. Base64 adds about a third on top of the original image size, and the result is still a bitmap that blurs when scaled. Find these with:
grep -l '<image' -r icons/
The fix is to get or draw a real vector. Never "optimize" an embedded bitmap; replace it.
2. Auto-traced artwork. Converting a raster logo to vectors with an automatic trace produces paths with hundreds or thousands of tiny segments, plus jagged edges. The giveaway is a d attribute that runs for pages. Redrawing the mark with clean curves, or tracing more coarsely and cleaning up by hand, can cut such a file by an order of magnitude. Simplifying a complex crypto logo into an icon covers when and how far to simplify.
3. Excess precision. Coordinates like 12.004871 instead of 12. In a 24 or 32 unit viewBox, two decimal places is plenty, and every extra digit is repeated across every point of every path. Rounding alone often removes a large share of the file.
4. Editor leftovers. Metadata blocks, editor namespaces, comments, hidden layers, unused gradients and empty groups. None of it renders. Optimizing SVG files without breaking them lists what can go and what must stay.
5. Text converted to outlines. A wordmark becomes a set of letter shapes, each with many curves. That is unavoidable for a full logo lockup, but for an icon, the symbol alone is usually the right choice anyway.
6. Duplicated or stacked shapes. The same shape exported twice, a shadow built from several offset copies, or a stroke and its outlined version both left in the file. Look for paths with identical or nearly identical d values.
7. Effects. Filters such as blurs and drop shadows add only a little markup, but they cost rendering time and usually look muddy at icon sizes. If a logo depends on a blur to look right, it is worth having a flat icon version.
Why the numbers matter, and when they do not
For one logo on a landing page, 2 KB versus 6 KB makes no practical difference. Size starts to matter with quantity:
- A token list showing 100 logos at 3 KB each is about 300 KB of SVG before compression. At 15 KB each, it is 1.5 MB.
- Inline SVG adds those bytes to your HTML or JavaScript bundle, not just to cached image requests, and every path becomes part of the DOM.
- Complex paths take longer to parse and rasterize. A grid with hundreds of heavy logos can scroll noticeably worse on low-end phones, even when the network was fast.
So the budget is less about any one file and more about the page that shows many of them at once. Rendering thousands of icons in one page covers the rendering side.
Setting a budget
A budget works when it is specific and checked automatically. A reasonable starting point for a logo library:
| Rule | Threshold |
|---|---|
| Warn | Any file over 8 KB |
| Fail | Any file over 20 KB, unless explicitly allowed |
| Fail | Any <image> element |
| Fail | Any file without a viewBox |
Adjust the numbers to your library. A set of single-color interface icons could use a 2 KB warning; a set of detailed brand logos might need more room. What matters is that a new 200 KB file cannot slip in unnoticed.
A small Node script can enforce it in CI:
// check-svg-budget.mjs
import { readdir, readFile } from 'node:fs/promises'
import { join } from 'node:path'
import { gzipSync } from 'node:zlib'
const dir = process.argv[2] ?? 'icons'
const WARN = 8 * 1024
const FAIL = 20 * 1024
const allowed = new Set(['detailed-logo.svg'])
let failed = false
for (const file of await readdir(dir, { recursive: true })) {
if (file.endsWith('.svg') === false) continue
const buf = await readFile(join(dir, file))
const text = buf.toString('utf8')
const size = `${buf.length} B, ${gzipSync(buf).length} B gzipped`
const problems = []
if (text.includes('<image')) problems.push('embedded <image>')
if (text.includes('viewBox') === false) problems.push('no viewBox')
if (buf.length > FAIL && allowed.has(file) === false) problems.push(`over budget (${size})`)
if (problems.length > 0) {
console.error(`FAIL ${file}: ${problems.join(', ')}`)
failed = true
} else if (buf.length > WARN) {
console.warn(`warn ${file}: ${size}`)
}
}
process.exit(failed ? 1 : 0)
Run it with node check-svg-budget.mjs icons after your optimization step. The recursive option of readdir needs a recent Node.js version (20 or later is a safe choice).
Fix at the source
When a file fails the budget, resist turning up compression settings until it passes. Aggressive rounding on a traced or bitmap-backed file just produces a smaller bad file. Find which cause applies, fix the artwork or replace it, then let your normal optimization settings, such as the ones in SVGO settings that are safe for icons, do the rest.