Concept
SVG (Scalable Vector Graphics) is an XML-based vector image format that, critically, is also a real part of the DOM when embedded inline. Every <circle>, <path>, <rect> is a genuine DOM element, selectable with document.querySelector, stylable with CSS, animatable, and event-listenable, exactly like any HTML element. This is the fundamental contrast with Canvas (covered in its own topic): SVG is retained-mode, Canvas is immediate-mode.
Basic shapes and the coordinate system
<svg viewBox="0 0 100 100" width="200" height="200">
<circle cx="50" cy="50" r="40" fill="#4f46e5" />
<rect x="10" y="10" width="30" height="30" fill="#10b981" />
<line x1="0" y1="0" x2="100" y2="100" stroke="black" stroke-width="2" />
<path d="M10 10 L90 10 L50 90 Z" fill="orange" /> <!-- triangle -->
</svg>viewBox defines the internal coordinate system (min-x min-y width height), separate from the element's actual displayed width/height. This is what makes SVG "scalable": the shapes are defined in viewBox units, and the browser scales that entire coordinate space to fit the displayed size, so the graphic stays crisp at any resolution (unlike a raster image, which pixelates when scaled up). A viewBox="0 0 100 100" displayed at width="500" just means every unit in your path data is scaled 5x on render, you never have to redraw at a specific pixel size.
<path>: the workhorse element
<path d="M10 10 L90 10 L50 90 Z" />
<!-- ^move ^line ^line ^close -->Path data (d attribute) is a mini-language of commands: M/m (move to), L/l (line to), C/c (cubic bezier curve), A/a (arc), Z/z (close path), uppercase means absolute coordinates, lowercase means relative to the current point. Essentially every complex icon or illustration you see rendered as SVG (from an icon library, or exported from Illustrator/Figma) is one or more <path> elements with generated d data, hand-writing complex path data is rare; it's produced by design tools or code.
Styling with CSS
<svg viewBox="0 0 24 24" class="icon">
<path d="M12 2L2 7l10 5 10-5-10-5z" />
</svg>.icon path {
fill: currentColor; /* inherits the surrounding text color, the standard icon pattern */
transition: fill 0.2s;
}
.icon:hover path {
fill: #4f46e5;
}Because inline SVG elements are real DOM nodes, ordinary CSS selectors, :hover, transitions, and currentColor all just work, fill="currentColor" is the standard technique that lets an icon automatically match surrounding text color (including on theme/color changes) without any JS.
Embedding strategies
<!-- 1. Inline, full DOM/CSS/JS access, no extra HTTP request, but bloats HTML if repeated -->
<svg viewBox="0 0 24 24"><path d="..." /></svg>
<!-- 2. <img>, simple, cacheable as a separate file, but NOT stylable with CSS from the parent page, NOT part of the accessible DOM tree -->
<img src="icon.svg" alt="Search" />
<!-- 3. CSS background-image, same limitations as <img>, purely decorative use -->
<div style="background-image: url('icon.svg')"></div>
<!-- 4. <object>/<iframe>, full SVG functionality (scripts run) but heavier, separate document context -->
<object data="chart.svg" type="image/svg+xml"></object>This choice matters and is frequently made without thinking about it. Only inline SVG (or <object>) is styleable by the host page's CSS and reachable by document.querySelector, an <img src="icon.svg"> is treated like any other image: no CSS fill override, no JS access to its internals, but it is a single cacheable network resource and doesn't bloat your HTML if the same icon repeats many times.
Accessibility
<!-- Meaningful/informative SVG: needs an accessible name -->
<svg role="img" aria-label="Search"><path d="..." /></svg>
<!-- Or with a <title> element (works even without inline embedding in some contexts) -->
<svg role="img" aria-labelledby="svg-title">
<title id="svg-title">Search</title>
<path d="..." />
</svg>
<!-- Purely decorative (adjacent to visible text that already says "Search"): hide it -->
<svg aria-hidden="true"><path d="..." /></svgUnlike Canvas, an inline SVG shape is part of the accessibility tree by default, but browsers vary in whether they announce anything meaningful without explicit role="img" + a name, always be explicit rather than relying on default behavior. If an icon is purely decorative (sitting next to text that already conveys the same meaning, like a search icon next to the word "Search"), aria-hidden="true" correctly removes the redundant announcement.
SVG sprite sheets (icon systems)
<!-- Define once, hidden -->
<svg style="display:none">
<symbol id="icon-search" viewBox="0 0 24 24">
<path d="..." />
</symbol>
<symbol id="icon-close" viewBox="0 0 24 24">
<path d="..." />
</symbol>
</svg>
<!-- Reference anywhere -->
<svg class="icon"><use href="#icon-search" /></svg>
<symbol> + <use> is the standard pattern for icon systems, define each icon's path data once, reference it by ID anywhere on the page without duplicating markup, while each <use> instance can still be styled independently via CSS.
Animation
<circle cx="50" cy="50" r="40">
<animate attributeName="r" from="40" to="60" dur="1s" repeatCount="indefinite" />
</circle>/* Or, more commonly in practice, plain CSS animation on the DOM elements */
.pulse { animation: pulse 1s infinite; }
@keyframes pulse {
to { r: 60; transform: scale(1.2); }
}Native SMIL animation (<animate>) works but is largely superseded by CSS animations/transitions in practice, since inline SVG elements are ordinary DOM nodes CSS can already target, most production SVG animation is just CSS (or a JS animation library like GSAP/Motion) applied to SVG DOM elements exactly as you'd animate any other element.
Common Mistakes
1. Choosing <img src="icon.svg"> then being confused CSS fill doesn't work
/* Does nothing, img-embedded SVG is opaque to the host page's CSS */
img.icon { fill: blue; }If you need CSS control over an icon's colors on hover/theme change, it must be inline SVG (or use <use> referencing an inline sprite), not an <img>.
2. Missing viewBox, breaking responsive scaling
<!-- Without viewBox, only width/height control size, no internal coordinate scaling -->
<svg width="24" height="24"><path d="..." /></svg>Without viewBox, an SVG doesn't scale its internal content proportionally the way you'd expect when you resize it via CSS, always include viewBox matching your design's coordinate space, even if width/height are also set.
3. No accessible name on meaningful icon-only buttons
<!-- Wrong: icon-only button, screen reader announces nothing useful -->
<button><svg viewBox="0 0 24 24"><path d="..." /></svg></button>Either aria-label on the <button> itself, or role="img" + aria-label/<title> on the <svg>, an icon-only interactive control with no accessible name is a common, easy-to-miss accessibility bug.
4. Massive, unoptimized path data shipped to production
Design tools (Figma, Illustrator) often export SVG with excessive precision, redundant groups, and editor metadata. Run exported SVGs through SVGO before shipping, this can reduce file size 50-90% with zero visual difference.
5. Treating <use href="#icon-search"> targets as globally safe without checking IE/legacy support if relevant
Modern browsers support unprefixed href on <use> universally now, but very old tooling/polyfill setups sometimes still reference the deprecated xlink:href, worth a note only if supporting genuinely legacy environments, otherwise a non-issue today.
6. Using SVG for photographic content
SVG is a vector format, it's the wrong tool for photographs or complex raster imagery (which have no meaningful "vector" representation); use <img>/<picture> with an actual raster format (WebP/AVIF/JPEG) for those.
Best Practices
- Inline SVG (or
<symbol>/<use>sprites) for any icon that needs CSS styling, hover states, or theme-color inheritance (fill="currentColor"). <img>/CSSbackground-imagefor genuinely static, non-interactive decorative SVGs where the caching/simplicity tradeoff makes sense.- Always include
viewBoxmatching your artwork's coordinate space. - Explicit accessible name (
role="img"+aria-label/<title>) on every meaningful SVG; on purely decorative ones adjacent to equivalent text.
Further Resources
- MDN, SVG
- MDN, SVG
<path>and thedattribute reference - CSS-Tricks, Using SVG and SVG symbol sprite guide
- SVGO, SVG optimizer
