1. Presentation
  2. Images
  3. SVG
  4. SVG and Vector Images

Presentation · Images 03

SVG and Vector Images

SVG is an image format that is also a markup language. That one fact explains everything about it: why it scales without blurring, why CSS can color it, why it compresses so well, why it can be accessible, and why it can be dangerous.

What an Image Costs set the rule for the section: SVG for anything drawn, raster for anything photographed. This page is about the drawn half. It covers how SVG describes a picture, the three ways to put one on a page and what each way allows, how to style, label and reuse SVG, how to make the files small, the one security rule that matters, and when CSS alone can do the drawing.

1. SVG is markup

SVG (Scalable Vector Graphics) is an XML language for two-dimensional drawings, a W3C standard since 2001 and drawn natively by every browser. Microsoft's VML was an earlier attempt at the same idea in Internet Explorer; SVG is the one that lasted. An SVG file is plain text: elements for shapes, attributes for their geometry and paint, and the same tree structure as an HTML document.

Here is the search icon this section uses, as it appears inside the site's icon file:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="10.5" cy="10.5" r="6.5"/> <path d="M15.5 15.5 20 20"/> </svg>

Read it as drawing instructions. A circle centered at (10.5, 10.5) with a radius of 6.5 is the lens. A path that moves (M) to (15.5, 15.5) and draws a line to (20, 20) is the handle. Nothing is filled, every outline is stroked two units wide with rounded ends, and the stroke color is currentColor, which section 3 explains. The whole icon, written out readably, is under 250 bytes of text.

Shapes and paths

SVG has a small set of basic shapes: <rect>, <circle>, <ellipse>, <line>, <polyline> and <polygon>. Everything else is a <path>, whose d attribute is a compact mini-language of pen movements: M moves, L draws a line, H and V draw horizontal and vertical lines, C and Q draw cubic and quadratic Bézier curves, A draws an arc, and Z closes the shape. Uppercase letters use absolute coordinates and lowercase ones are relative to the current point. Drawing tools write almost everything as paths, which is why exported SVG is full of long d strings.

Shapes can be grouped with <g>, moved and scaled with transform, and painted with gradients and patterns. SVG also has real text in <text>, which stays selectable and searchable, unlike text drawn into a raster image.

The viewBox

The numbers in an SVG have no unit. They are in the drawing's own coordinate system, called user units, and the viewBox attribute says which rectangle of that system is the picture: viewBox="0 0 24 24" means "the area from (0, 0), 24 units wide and 24 tall". The size the drawing occupies on the page, its viewport, is set separately, by width and height attributes or by CSS. The browser maps the viewBox onto the viewport, scaling every coordinate to fit.

One viewBox drawn into three viewportsOn the left, the search icon on its 24 by 24 grid of user units, the viewBox. To its right, the same drawing scaled into a 48 by 48 viewport and a 96 by 96 viewport, sharp at both sizes. On the far right, a wide 150 by 80 viewport: with the default preserveAspectRatio of xMidYMid meet, the square viewBox is scaled to fit the height and centered, leaving empty space on either side.viewBox: 24 × 24 user units024viewBox="0 0 24 24"two square viewports48 × 4896 × 96a wide viewport150 × 80xMidYMid meetfit, centered, no distortion
The viewBox is the drawing's coordinate system; the viewport is the box on the page. The same 24-unit drawing fills a 48-pixel and a 96-pixel box with no new file and no blur.

This separation is what makes SVG scalable. The file describes the shape once, in its own units, and the page decides how big to draw it. An icon designed on a 24-unit grid can be drawn at 16 CSS pixels in a footer and 96 in a hero, and on a high-density screen the browser draws it at the full device resolution. The device pixel ratio that makes raster images four times as expensive costs SVG nothing.

When the viewport's proportions differ from the viewBox's, the preserveAspectRatio attribute decides what happens. The default, xMidYMid meet, scales the drawing to fit entirely inside the viewport and centers it, like object-fit: contain. slice instead fills the viewport and crops the overflow, like cover, and none stretches the drawing to fill both directions, distorting it. Most of the time the default is right, and the better fix is to give the viewport the drawing's own proportions.

Raster inside vector

An SVG can contain things that are not vectors. The <image> element places a raster image inside the drawing, and <foreignObject> embeds a fragment of another language, usually HTML. Both are useful, for a photograph inside a diagram or a paragraph of wrapping text inside a shape, but neither becomes a vector by being inside one: the raster image blurs when the drawing scales up, exactly as it would in an <img>. "Scalable" describes the shapes, not the file's whole contents.

2. Three ways in: <img>, inline <svg>, and CSS

Because SVG is both an image and a document, there are three ordinary ways to put one on a page, and they are not interchangeable. The difference is where the SVG's document lives: in its own file, isolated from the page; inside the page's DOM; or in a style sheet, as decoration.

Where the SVG lives in each of the three ways inA large box is the page's DOM. Inside it, an img element points out of the page to logo.svg, a separate document, where scripts do not run and page CSS stops at the edge, and which is cached on its own. Also inside the DOM, an inline svg element has its own child nodes, a circle and a path, in the page's own tree, where page CSS and scripts reach every node, but whose bytes repeat in every page's HTML. At the bottom, a style sheet's url() supplies an image to a pseudo-element inside the DOM as decoration, with no alt text.the page's DOM<img>logo.svga separate documentno scripts; page CSS stops at its edge;cached on its own, like any image<svg><circle><path>inline: nodes in the page's own tree;page CSS and scripts reach every one;the bytes repeat in every page's HTMLa::afterstyles.css: url(icon.svg)decoration only: no alt, no DOM
An <img> points at a separate document. An inline <svg> is part of the page's own tree. A CSS url() paints decoration onto an element.

As an image: <img src="logo.svg">

An SVG in an <img> behaves like any other image. It is a replaced element: the browser fetches the file and draws it into the element's box, and the page's DOM contains the <img>, not the shapes. It takes alt, width and height, it can be lazy-loaded, and it is cached on its own, so a logo used on every page of a site is downloaded once.

The SVG is also a separate document, and browsers deliberately restrict what that document can do. When SVG is used as an image:

  • scripts inside it do not run, and its links and other interactive parts do nothing;
  • it cannot load anything else: no external style sheets, web fonts or images (anything it needs must be inside the file);
  • the page's CSS cannot reach inside it, so fill rules in the page's style sheet have no effect on its shapes;
  • its own <style> and its declarative animations do work.

Those restrictions are a feature. An image tag should be safe to point at a file, and in an <img> an SVG is about as inert as a PNG. The cost is that the page cannot recolor it: a logo that must change with the theme has to carry the change itself, as the section's logo does with a prefers-color-scheme rule inside the file.

Trail Notes logo: a gold mountain in a navy circle, with the words Trail Notes.
The section's logo, 900 bytes of SVG in an <img>. Its own style element darkens or lightens the word "Trail" with the color scheme, where the browser supports media queries inside SVG images.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 240" width="480" height="240"> <title>Trail Notes logo</title> <style>@media (prefers-color-scheme: dark) { .ink { fill: #e8ebf2; } }</style> <circle cx="120" cy="120" r="96" fill="#1d2b53"/> … <text x="236" y="112" … fill="#1d2b53" class="ink">Trail</text> </svg>

Inline: <svg> in the HTML

HTML's parser accepts <svg> elements directly in a page, without the xmlns declaration a standalone file needs. An inline SVG is not an image at all in the <img> sense: its shapes are elements in the page's DOM, alongside the paragraphs. Page CSS can select and style every one of them, scripts can change them and listen for events on them, and links inside them work. Charts that respond to hover, icons that change color with a button's state, and diagrams that follow the site's theme are inline.

The costs are the mirror image of <img>'s benefits. Inline SVG is not cached separately: its bytes are part of the HTML, so a 4 KB illustration on every page is 4 KB more on every page's download, every time the HTML is fetched. It adds nodes to the DOM. It cannot be lazy-loaded with an attribute. And because it is part of the page, anything in it runs with the page's full authority, which section 7 returns to.

In CSS: background-image and mask-image

A style sheet can use an SVG anywhere it accepts an image: background-image, list-style-image, border-image, content. Like <img>, the SVG is a separate, restricted, cached document. Unlike <img>, it is decoration: it has no alternative text, it is not in the DOM, and assistive technology never sees it. Use CSS for images that would only make the page look plainer if they were missing, as What an Image Costs describes, and see Images in CSS for the properties.

One CSS technique is worth knowing here because it solves the recoloring problem. mask-image uses an image's shape as a stencil: where the mask is opaque, the element shows; where it is transparent, the element is hidden. Paint the element with background-color: currentColor and mask it with an SVG, and the icon takes the text color, even though the SVG is an external file. mask-image without a prefix has been Baseline since December 2023.

@layer components { a[href^="https:"]::after { content: ""; display: inline-block; inline-size: 0.8em; block-size: 0.8em; margin-inline-start: 0.25em; background-color: currentColor; mask: url("external.svg") center / contain no-repeat; } }

Choosing

<img>Inline <svg>CSS url()
Cached separatelyYesNo: part of the HTMLYes
Page CSS styles its shapesNoYesNo (but mask can recolor it)
Scripts and interactionNoYesNo
Loads external fonts and imagesNoYesNo
Accessible namealt<title> or ARIANone: decoration only
Lazy loadingloading="lazy"NoLoads when the rule applies
Use it forLogos, illustrations, diagrams that need no stylingIcons, themed or interactive graphicsDecorative backgrounds, markers, masks

Two older ways still exist. <object data="drawing.svg"> and <iframe> load the SVG as a full, separate document whose own scripts run. They were the only way to get an interactive external SVG before inline SVG worked everywhere, and today they are rarely the right choice.

3. Styling SVG: currentColor, custom properties, dark mode

The deck's old slide says SVG is styleable with CSS "though to a limited extent". The limit is narrower than it sounds. Inline SVG elements take CSS like any other element, through selectors, the cascade and inheritance. What differs is the property names. SVG paints with fill (the inside of a shape) and stroke (its outline), sized by stroke-width, and its text uses fill rather than color. A shape's default fill is black and its default stroke is none.

Presentation attributes lose to any CSS rule

Attributes such as fill="#1d2b53" and stroke-width="2" are presentation attributes. They are style, written in the markup, and they count as author styles with a specificity of zero: any CSS rule that selects the element overrides them. That makes them good defaults in a file, which a page can then restyle. One detail matters for icons: a presentation attribute on an element still beats a value the element would only inherit from its parent. A shape with fill="black" stays black whatever color its parent is given.

currentColor

The keyword currentColor means "the value of color here". An icon drawn with stroke="currentColor" or fill="currentColor" has no color of its own; it takes the text color of whatever it sits in. Put it in a link and it is the link color, including on hover. Put it in a button and it is the button's text color. Switch to dark mode and it follows the text. Nobody writes a rule for the icon at all.

@layer base { :root { --link: #a3420f; @media (prefers-color-scheme: dark) { --link: #f0a270; } } a { color: var(--link); &:hover { color: inherit; } svg { inline-size: 1.25em; block-size: 1.25em; vertical-align: -0.2em; } } }

Sizing the icon in em does the same for size that currentColor does for color: the icon follows the font size of its context. That is the whole icon system's styling in a few lines, and it adapts to the reader's text size and color scheme without any per-icon rules.

Custom properties

One color is not always enough. A two-tone icon can use currentColor for one part and a custom property for the other. Custom properties inherit, like color, so a value set on the page reaches shapes inside an inline SVG and inside a <use> clone (section 5), where page selectors cannot.

<symbol id="icon-pin" viewBox="0 0 24 24"> <path d="M12 21s-6.5-5.6-6.5-11a6.5 6.5 0 0 1 13 0c0 5.4-6.5 11-6.5 11z" style="fill: var(--icon-fill, none)" stroke="currentColor"/> <circle cx="12" cy="10" r="2.5" fill="currentColor"/> </symbol> nav a[aria-current="page"] { --icon-fill: oklch(85% 0.12 85); }

The var() goes in a style attribute or a <style> rule, where every browser resolves it as CSS. Support for var() written directly in a presentation attribute has varied between browsers, so do not rely on it. The fallback, none, keeps the icon an outline wherever the page sets nothing. The icon declares which values it takes from outside, and the page supplies them.

Dark mode

For inline SVG, dark mode is ordinary CSS: colors come from custom properties or currentColor, and the page's prefers-color-scheme query or light-dark() values (Baseline since 2024) change them. For SVG in an <img> or CSS, the file must adapt itself with a query in its own <style>, as the logo does, or the page must swap files, which Images in HTML shows with <picture> and a media condition on a <source>.

4. Accessible SVG

Every image is either content or decoration, and SVG follows the same rule as any other image: content gets a text alternative, decoration is hidden from assistive technology. What changes is the mechanism, which depends on how the SVG arrived.

In an <img>: use alt

An SVG in an <img> is labelled exactly like a JPEG: alt with the text alternative, or alt="" if it is decorative. A <title> inside the file is not a substitute, because the file is a separate document that screen readers do not look into. Images for People covers how to write the alt text itself.

Inline: role="img", <title>, <desc>

An inline SVG is a tree of elements, and without help, assistive technology may announce it as a group, read the text nodes inside it one by one, or skip it. To present a meaningful inline SVG as one image with a name:

  • role="img" on the <svg> says "treat this whole subtree as one image";
  • <title>, the first child, is the short name, like alt text;
  • <desc> is a longer description, for a diagram or chart whose content needs more than a name;
  • aria-labelledby pointing at both ids makes the name and description explicit, which is more reliable across browser and screen reader pairings than relying on <title> alone.
<svg viewBox="0 0 660 230" role="img" aria-labelledby="vb-title vb-desc"> <title id="vb-title">One viewBox drawn into three viewports</title> <desc id="vb-desc">On the left, the search icon on its 24 by 24 grid …</desc> … </svg>

The same <title> often shows as a tooltip when the pointer rests on the drawing. Write the description for a listener who cannot see the picture at all: the diagrams on this page describe what is drawn and what it shows, not "a diagram".

Decorative SVG: aria-hidden="true"

An icon beside a visible word, such as a magnifying glass next to "Search", adds nothing for a screen reader user, and announcing it would only repeat or clutter the label. Hide it with aria-hidden="true" on the <svg>, and leave the name to the text.

An icon with no visible text is different: it is the only label the control has. The name then belongs on the control, not the drawing: aria-label on the <button> or link, with the SVG inside still hidden. Naming the button rather than the icon keeps the name in one predictable place, and it survives if the icon is swapped.

<button> <svg aria-hidden="true"><use href="/icons.svg#icon-search"/></svg> Search </button> <button type="button" aria-label="Favorite this trail" aria-pressed="false"> <svg aria-hidden="true"><use href="/icons.svg#icon-star"/></svg> </button>
The SVG is…Delivered asWrite
Content<img>alt="…"
ContentInline <svg>role="img", <title>, <desc> if needed, aria-labelledby
Decoration<img>alt=""
Decoration, or an icon beside visible textInline <svg>aria-hidden="true"
The only label of a controlInline <svg>aria-label on the button or link; aria-hidden="true" on the SVG
DecorationCSSNothing: background images are invisible to assistive technology

5. Icon systems: <symbol> and <use>

A site with thirty icons used on every page has a choice. Thirty <img> files are thirty requests and cannot take the text color. Thirty inline SVGs repeat their path data in every page's HTML, every time the icon appears. The SVG answer is a sprite: one file that defines every icon once, as a <symbol>, and a short <use> reference wherever an icon is drawn.

A <symbol> is a drawing that is never rendered by itself. It has its own viewBox and an id, and it appears only where a <use> element points at it. The section's icon file holds four:

<svg xmlns="http://www.w3.org/2000/svg"> <symbol id="icon-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <title>Next</title><path d="M5 12h14M13 6l6 6-6 6"/> </symbol> <symbol id="icon-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <title>Search</title><circle cx="10.5" cy="10.5" r="6.5"/><path d="M15.5 15.5 20 20"/> </symbol> … </svg>

A page then draws an icon with an empty inline <svg> wrapping a <use>, whose href names the file and, after the #, the symbol:

<a href="/trailheads/"> <svg aria-hidden="true"><use href="/media/icons.svg#icon-pin"/></svg> Trailheads </a>
One sprite file, referenced by use elements on many pagesOn the left, the file icons.svg holds four symbols: icon-arrow, icon-search, icon-pin and icon-star. On the right, three use elements on different pages each point at icons.svg#icon-search, and lines connect all three to the one search symbol. The file is fetched once and cached, and must come from the same origin as the page.icons.svg<symbol> #icon-arrow<symbol> #icon-search<symbol> #icon-pin<symbol> #icon-starthree pages, one symbol<use href="icons.svg#icon-search"/><use href="icons.svg#icon-search"/><use href="icons.svg#icon-search"/>fetched once and cached · same origin as the page · recolored by inheritance
The sprite is downloaded once. Each <use> is a few dozen bytes of HTML that clones a symbol into the page where it appears.

What the clone inherits

The browser draws a <use> by cloning the symbol into a hidden shadow tree attached to the <use> element. Page selectors cannot reach into that tree: a svg path { stroke: red; } matches nothing inside it. Inheritance does reach in. That is why the sprite's symbols draw with stroke="currentColor": the clone inherits color from the <use>, which inherits it from the link or button, so one rule on the surrounding text colors the icon. Custom properties inherit the same way, which is how section 3's second color reaches a cloned icon. A value the symbol hard-codes as an attribute, by contrast, cannot be changed from outside.

Same origin only

A <use> that points at another file only works when that file comes from the same origin as the page: the same scheme, host and port. Browsers refuse cross-origin sprite references, and there is no CORS opt-in for <use>, so a sprite on a CDN under a different host name will not load. Serve the sprite from the site's own origin, or, if it must live elsewhere, paste the symbols into the page as an inline, hidden <svg> and reference them by fragment alone (href="#icon-search"). Referencing a sprite through a data: URL in <use> is deprecated for security reasons. Opening the demo below from a file:// URL can also fail, because local files may not count as the same origin; serve it over HTTP.

The sprite and the older raster sprite

Before SVG sprites, sites combined small raster icons into one PNG and showed each one by shifting a background image by pixel offsets. The site has that sheet too, icons-sprite.png at 192 by 48 pixels, for comparison:

.icon-search { width: 48px; height: 48px; background: url(icons-sprite.png) -48px 0 no-repeat; }

That sheet is one color, one size and one density: the icons blur when enlarged, cannot follow the text color, and must be re-cut whenever the design changes. Image Performance explains why HTTP/2 made raster sprites mostly unnecessary. The SVG sprite remains worth having, because its benefit was never only the request count.

The section's four icons, two ways, as encoded by the site's build script
FileBytesSizesColors
icons.svg1,039AnyAny, from currentColor
icons-sprite.png2,32248 pixels, one densityThe one it was drawn in

6. Optimizing SVG

Because SVG is text, it can be made smaller the way any markup can, and it is often far larger than it needs to be. The waste comes from the tools that write it.

Editor output

Drawing programs export SVG for round trips back into the same program, not for browsers. A typical export carries:

  • an XML declaration, a comment naming the generator and sometimes a DOCTYPE;
  • editor namespaces and metadata (sodipodi:, inkscape:, <metadata> with licensing RDF);
  • coordinates with five or six decimal places, where one or two would draw identically;
  • empty groups, unused <defs>, hidden layers and default values written out in full;
  • shapes as long absolute paths where shorter relative commands would do.
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!-- Created with a vector editor --> <svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:sodipodi="…" xmlns:inkscape="…" width="24" height="24" viewBox="0 0 24.000000 24.000000" version="1.1" id="svg8" inkscape:version="1.3" sodipodi:docname="search.svg"> <metadata id="metadata5">…</metadata> <g inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1"> <g id="g12"> <circle style="fill:none;stroke:#000000;stroke-width:2.0000000;stroke-opacity:1" id="path10" cx="10.500000" cy="10.500000" r="6.5000000" /> …

None of that changes a pixel. It also hard-codes #000000, which defeats currentColor. The fix is a tool, not hand editing.

SVGO and SVGOMG

SVGO is the standard SVG optimizer, a Node.js tool with a plugin for each kind of cleanup: removing metadata and comments, rounding coordinates, merging paths, collapsing groups, shortening colors and path data. SVGOMG, by Jake Archibald, is SVGO in a web page: paste or drop in a file, toggle each plugin, and watch the preview and the byte count change. Use SVGOMG to learn what each option does and to check a single file; run SVGO in the build so every SVG in the project is optimized the same way, every time.

Optimizers can break things, so look at the result. The two classic mistakes were removing the viewBox, which stops the drawing from scaling, and removing <title>, which removes an accessible name. SVGO 4, released in 2025, no longer does either by default; older versions and copied configurations may. Too much rounding distorts small icons, and merging paths can break a shape that CSS or a script targets by id or class. Keep the original as the master and treat the optimized file as build output.

Compression

The server does the rest. Text with repeated element names and attribute names compresses very well with gzip and Brotli, the two compression schemes every browser accepts in Content-Encoding. These are the section's own SVG files, already small, compressed at each tool's highest setting:

FileBytesgzipBrotli
icons.svg1,039385316
logo.svg900524445

The icon file shrinks by about 70 percent, because its four symbols repeat the same attributes. JPEG, WebP and AVIF are already compressed, so servers do not compress them again; SVG is the one image format that gains from it. Check that the server sends SVG with Content-Type: image/svg+xml and a Content-Encoding: a misconfigured server that sends it uncompressed, or with the wrong type, wastes the advantage or breaks the image.

Measure

SVG is not small by nature. A simple icon is a few hundred bytes, but a detailed illustration, a map, or a photograph traced into paths can run to hundreds of kilobytes, more than the same picture as WebP, and the browser must parse and draw every path, on every repaint. Compare the optimized, compressed SVG against a raster version at the size it is displayed, in the DevTools Network panel's transferred column, and choose the smaller one that looks right. Image Performance covers the measuring tools.

7. Never inline untrusted SVG

An SVG file can contain a <script> element, onload and other event-handler attributes, javascript: links, and <foreignObject> holding arbitrary HTML. In an <img> none of that runs, which is why <img> is the safe way to show an SVG you did not write. Everywhere else it can run:

  • Inline, the SVG's scripts and handlers run as part of the page, with the page's cookies, storage and DOM. Pasting a user-supplied SVG into the HTML is a cross-site scripting hole.
  • Opened directly, an uploaded SVG served from the site's own origin is a document of its own, and a link to it runs its scripts on that origin.
  • In <object> or <iframe>, it is again a full document with scripts.

The rule is simple: never inline an SVG from an untrusted source, whether a user upload, a third-party API or a pasted snippet. If a site accepts SVG uploads, show them only through <img>, serve them from a separate domain with a Content-Security-Policy that forbids scripts, and run them through a real sanitizer such as DOMPurify (configured for SVG) before storing them. An optimizer that removes <script> elements is not a sanitizer, because there are many other places to hide script. Accepting only raster uploads, or converting SVG uploads to PNG on the server, avoids the problem entirely.

8. Drawing with CSS

The deck's "Tricky CSS" slide pointed at a different kind of vector drawing: shapes made from CSS alone. Borders, border-radius, gradients, clip-path and pseudo-elements can draw circles, triangles, rings, pie charts, even portraits of cartoon characters, with no image file at all. CSS is resolution independent too, so these drawings stay sharp at any size and density.

@layer components { :where([data-ring]) { --value: 70%; --ring: currentColor; --track: color-mix(in oklch, currentColor 20%, transparent); display: inline-grid; place-items: center; inline-size: 4em; aspect-ratio: 1; border-radius: 50%; background: conic-gradient(var(--ring) var(--value), var(--track) 0); mask: radial-gradient(farthest-side, transparent 70%, black 71%); } }

The ring is decoration: if it shows a real value, the number belongs in the HTML as text beside it, or the element should be a <progress> or <meter>.

When it is worth it: small, geometric, decorative shapes that are really part of the layout, such as a divider, a status dot, a ring, a gradient background or a disclosure arrow. They cost no request, follow custom properties and currentColor, and animate cheaply. Gradients are images in CSS's sense, as Images in CSS shows.

When it is not: anything with meaning. A CSS pie chart has no accessible name, no text alternative and no structure for assistive technology unless the HTML around it supplies all three, and it is harder to read and change than an SVG of the same chart. Elaborate CSS illustrations are clever demonstrations, often larger than the equivalent SVG, and brittle to maintain. If the shape is content, draw it in SVG and label it; if it is a flourish, CSS is fine.

9. Summary

ConceptKey points
SVG is markupAn XML language of shapes and paths. Coordinates are user units inside the viewBox; the viewport sets the drawn size, so one file scales sharply to any size and density. preserveAspectRatio defaults to xMidYMid meet. Raster content inside an SVG does not become scalable.
Three ways in<img>: a separate, cached, inert document with alt; page CSS cannot reach in. Inline <svg>: part of the DOM, styleable and scriptable, but not cached separately. CSS url(): decoration only, recolorable with mask.
Stylingfill and stroke; presentation attributes lose to any CSS rule. currentColor and em sizes make icons follow their text; custom properties pass further values in. An <img> SVG must handle dark mode itself.
Accessible SVGalt for <img>. Inline content: role="img", <title>, <desc>, aria-labelledby. Decorative: aria-hidden="true". Icon-only controls: name the control.
Icon systemsOne sprite of <symbol>s, drawn with <use href="icons.svg#id">. Cached once, recolored by inheritance into the shadow tree, same origin only.
OptimizingEditor output is bloated; run SVGO in the build and use SVGOMG to inspect. Keep viewBox and <title>. Serve with gzip or Brotli. Measure against raster for complex art.
SecuritySVG can carry script. Show untrusted SVG only through <img>; never inline it; sanitize and isolate uploads.
Drawing with CSSFine for small decorative geometry; use SVG for anything that carries meaning.