Back to blog
10 min read

SVG isn't an image, it's code

SVG isn't an image format with transparency — it's an XML document the browser renders as graphics. What that gives designers and no-code developers.
Contents
  1. Two strategies
  2. As a file
  3. Inline
  4. When to use which
  5. What inline gives you (and a file can’t)
  6. 1. Recoloring via currentColor
  7. 2. Animation via CSS
  8. 3. JavaScript access
  9. viewBox — why your icon “shifts”
  10. SVG in no-code platforms
  11. Three small things often overlooked
  12. Optimization
  13. Security
  14. Accessibility
  15. Checklist: before embedding an SVG on a site
  16. Sources and where to dig deeper

Open any .svg file in a text editor. Notepad, VS Code — doesn’t matter. What do you see? XML — code you can read and edit:

<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
  <circle cx="12" cy="12" r="10" fill="currentColor"/>
</svg>

↑ this circle is the code above rendered. The browser read the XML and drew the graphic.

This is the single most important takeaway from this post. SVG isn’t a vector image with transparency, like many people think. It’s an XML document the browser renders as graphics.

Sounds like a nuance. In reality — it changes a lot. A designer who treats SVG as “just an image format” misses half of what it can do. A no-code developer who embeds SVG only as a file cuts themselves off from the interactive side: recoloring, animation, and other effects.

Let’s break down what that means in practice.


Two strategies

SVG can be embedded on a page in two fundamentally different ways. The choice determines what you can do with it afterwards.

As a file

As an <img> in HTML:

<img src="/icons/heart.svg" alt="Like" />

As a background-image in CSS:

.button {
  background-image: url("/icons/heart.svg");
}

Simple. Browser-cached. Works in any site builder via the standard image upload. For static decorative icons — perfect.

One downside, but a fundamental one: to CSS, this is a “black box”. The browser treats the SVG file as an isolated document — the same way it treats a PNG or JPEG. CSS has no access to the file’s contents. Want the icon to change color on hover? You’ll have to keep a second copy of the file in the right color and swap one for the other on hover — heavier than just inlining the SVG.

Inline

<svg width="24" height="24" viewBox="0 0 24 24">
  <circle cx="12" cy="12" r="10" fill="currentColor" />
</svg>

The SVG goes straight into the HTML and becomes part of the DOM. And here’s where it gets interesting: every element inside the SVG (<circle>, <path>, <rect>) is a full-fledged DOM node. CSS applies to it, JS event listeners attach to it, it can be animated.

Downsides: it’s duplicated everywhere it’s used. Not cached separately. The HTML gets slightly heavier.

When to use which

Simple rule:

  • Just need rendering (a static icon, an illustration in a corner) → file
  • Need recoloring (icon changes color on hover, follows the site theme) → inline
  • Need animation (hamburger expands, arrow rotates, something reacts) → inline
  • Need interactivity (clicking a region of an SVG map, hovering a chart sector) → inline

There’s also a hybrid option — SVG sprites with <use>, when the same icon is reused in dozens of places. That’s a separate topic, doesn’t fit in this post.


What inline gives you (and a file can’t)

1. Recoloring via currentColor

If you set fill="currentColor" or stroke="currentColor" in your SVG, the icon inherits its parent’s text color. One file — an infinite palette through CSS: theme variables, hovers, active states.

Important: currentColor only works in inline SVG. Not in <img src="..."> — external CSS can’t reach inside the file (the same “black box” from the previous section).1

<button class="like-btn">
  <svg width="24" height="24" viewBox="0 0 24 24">
    <path
      d="M12 21s-7-4.35-7-10a4 4 0 0 1 7-2.65A4 4 0 0 1 19 11c0 5.65-7 10-7 10z"
      fill="currentColor"
    />
  </svg>
  Like
</button>
.like-btn {
  color: #333;
  transition: color 0.2s;
}
.like-btn:hover {
  color: red; /* icon turns red too */
}
← hover the button

One SVG. Change just the CSS — the icon repaints. No more heart-red.svg, heart-blue.svg, heart-grey.svg copies.

This solves the very problem designers usually address by exporting five copies of the same icon. So — must-know.

2. Animation via CSS

Since every shape inside an SVG is a DOM element, any CSS applies to it, including transition and @keyframes.

Minimal example — a rotating arrow:

<svg width="24" height="24" viewBox="0 0 24 24" class="arrow">
  <path d="M9 6l6 6-6 6" stroke="currentColor" stroke-width="2" fill="none" />
</svg>
.arrow {
  transition: transform 0.3s;
}
.expanded .arrow {
  transform: rotate(90deg);
}
← CSS animation, no JS

No JS, no libraries. Just CSS on an SVG element. That’s how hamburger menu animations, checkbox states, loading spinners, and many other things are made.

3. JavaScript access

SVG elements can be selected with document.querySelector, you can attach event handlers, change attributes. This opens the door to interactive maps, charts, and diagrams.

I won’t go deeper here — just know that door is open.


viewBox — why your icon “shifts”

The one attribute that sometimes breaks how an SVG renders.

The easiest way to explain it: viewBox is a crop frame for the SVG’s internal canvas. Picture an infinite Figma-like canvas inside the SVG file, drawn in abstract units, and viewBox is a frame that tells the browser “show this piece of the canvas, crop the rest.”

The four values inside are min-x min-y width height: the top-left corner of the frame and its dimensions. viewBox="0 0 24 24" means a frame that starts at point (0, 0) and is 24×24 abstract units. Not pixels — these are SVG’s internal coordinates, the same way coordinates work inside a Figma frame.

Now width and height on the <svg> tag itself (or CSS sizes) — those are the physical size on the page. The browser takes the content inside the viewBox frame and stretches it to that physical size. So an SVG with viewBox="0 0 24 24" and width="48" appears twice as large — no quality loss, because it’s a vector.

In short: viewBox = what to show, width/height = at what size to show it. Two different layers people often confuse.

If, after exporting from Figma, your icon shifts, gets cropped, or behaves oddly — the problem is almost always viewBox or preserveAspectRatio.2 Open the SVG in an editor and look at those attributes manually.

More on MDN: viewBox.


SVG in no-code platforms

Quick rundown of the main ones:

Webflow. As a file — via the standard image asset (image as image). Inline — via the HTML Embed block: paste the SVG code, the icon becomes part of the DOM and accessible to CSS.

Framer.

  • As an image — drag&drop the SVG file onto the canvas. Framer treats it as a regular image; recoloring and per-part animation aren’t available.
  • As code — a Code Component in React. The most flexible path: props, state, complex animations. Higher barrier — you need to know React.

Tilda. Mostly — SVG as a file (via the standard image upload). Inline is possible through the T123 block (HTML code), but Tilda’s UI doesn’t help: the code is written and edited by hand. currentColor recoloring will only work if the SVG was embedded via T123, not as an image.

Rule of thumb: if a platform lets you embed arbitrary HTML — it can do inline SVG.


Three small things often overlooked

Optimization

Figma, Sketch, and Illustrator generously add junk when exporting SVG: extra <defs>, empty groups, metadata, IDs, unnecessary attributes. A 5 KB Figma icon easily becomes 1 KB after optimization. Same icon, same visual information — just without the bloat.

Running every file through SVGOMG before uploading — a must-have. It’s the web version of SVGO: open it, drop your file, download the optimized version. 30 seconds per icon.

Security

SVG is code. Which means it can contain <script>. Real JavaScript that runs in the browser.

What that means in practice: don’t embed SVG from untrusted sources via inline or Embed blocks. Especially if your site allows user file uploads (avatars, for example). An SVG from an anonymous user is a potential XSS attack vector.

As a file (<img src="...">) — safer: per the SVG spec, the browser switches such a file into secure static/animated mode — scripts, external resources, and interactivity are disabled.3 Inline — only things you’ve inspected manually in an editor.

Accessibility

If the SVG carries meaning (rather than being decorative), add a <title> inside:

<svg
  width="24"
  height="24"
  viewBox="0 0 24 24"
  role="img"
  aria-labelledby="delete-icon-title"
>
  <title id="delete-icon-title">Delete</title>
  <path d="..." />
</svg>

A screen reader will announce “Delete” — a blind user will know what they’re pressing. One line of code — and your project becomes more mature.

Decorative SVGs, on the other hand, are hidden from screen readers via aria-hidden="true".


Checklist: before embedding an SVG on a site

  • Need recoloring or animation? → inline. If not → file is fine
  • Run through SVGOMG?
  • viewBox in place? width/height not hardcoded unnecessarily?
  • For meaningful icons — is there a <title> inside the SVG?
  • Does the <title> have a unique id, and is <svg> set up with role="img" and aria-labelledby referring to that id?
  • For decorative icons — is aria-hidden="true" set on <svg>?
  • If the SVG is from an external source — did you open it in an editor and check for <script>?
  • If using currentColor — does the parent element set color correctly?

Sources and where to dig deeper


Footnotes

  1. MDN: currentColor — the keyword resolves to the computed color value of the parent. In <img> mode the SVG is isolated from the parent document’s CSS, so currentColor resolves to the color at the root of the SVG document itself (black by default).

  2. MDN: preserveAspectRatio — the attribute controls how viewBox content is scaled when the aspect ratio doesn’t match width/height. Nine alignment values (xMinYMin, xMidYMid, xMaxYMax, etc.) × two modes (meet — fit entirely, slice — fill and crop).

  3. SVG Integration — Processing Modes — W3C specification. An SVG loaded via <img>, <picture>, background-image, or border-image enters secure static mode (or secure animated mode if SMIL animations are present): scripts blocked, external resources don’t load, interactive handlers don’t fire. Via <object>, <iframe>, or inline — dynamic interactive mode, everything enabled.