Basic Structure
SVG Document Skeleton
The xmlns attribute declares the SVG namespace. width/height set the rendered size in pixels. viewBox defines the internal coordinate system. SVG can be a standalone .svg file or embedded inline in HTML, where the XML declaration and xmlns are optional.
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
width="200" height="200"
viewBox="0 0 200 200">
<!-- SVG content goes here -->
<rect x="0" y="0" width="200" height="200" fill="#eee"/>
</svg>Embedding SVG in HTML
Inline SVG is the most flexible — you can style it with CSS and script it with JS just like HTML. <img> and background-image treat SVG as a static image (no internal CSS/JS). <object> loads it as a separate document with its own DOM, allowing some scripting.
<!-- 1. Inline (best for styling and scripting) -->
<svg width="100" height="100">
<circle cx="50" cy="50" r="40" fill="red"/>
</svg>
<!-- 2. As an image (no CSS/JS interaction) -->
<img src="icon.svg" alt="Icon" width="100" height="100">
<!-- 3. As a background (limited styling) -->
<div style="background: url('icon.svg') no-repeat;"></div>
<!-- 4. Via object/embed (allows scripting) -->
<object data="icon.svg" type="image/svg+xml"></object>Coordinate System
SVG uses a left-handed coordinate system: origin at top-left, X right, Y down. The viewBox attribute defines the internal coordinate space — SVG scales content to fit the element's width/height, so a viewBox of 0 0 100 100 in a 200px element renders everything at 2x.
<svg width="200" height="100" viewBox="0 0 200 100">
<!-- Origin (0,0) is top-left, like Canvas -->
<!-- X increases rightward, Y increases downward -->
<!-- A point at (50, 25) -->
<circle cx="50" cy="25" r="3" fill="black"/>
<!-- The viewBox stretches content to fill width/height -->
<!-- viewBox="minX minY width height" -->
</svg>
<!-- viewBox 0 0 100 100 in a 200x200 element = 2x zoom -->
<svg width="200" height="200" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="50" fill="blue"/>
</svg>Width, Height & Units
User units (no suffix) are equivalent to px. Percentages are relative to the parent. For responsive SVG, omit width/height and set viewBox — the SVG scales to its container while preserving the aspect ratio. 'auto' on height with a viewBox keeps proportions.
<svg width="200" height="100">
<!-- Default unit is the user unit (px equivalent) -->
</svg>
<!-- Common units: px, em, %, pt, cm, mm, in -->
<svg width="50%" height="auto" viewBox="0 0 100 100">
<!-- % is relative to the parent element -->
</svg>
<!-- No width/height + viewBox = fully responsive -->
<svg viewBox="0 0 100 100" style="width:100%;height:auto;">
<!-- Scales to container, preserves aspect ratio -->
</svg>Namespaces & xlink
SVG 2 introduced direct href support on <use>, <image>, and <a>, deprecating xlink:href. Modern browsers support plain href. Keep the xlink namespace only if you need to support very old browsers. Inline SVG in HTML doesn't strictly need the xmlns attribute.
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Modern SVG 2: use href directly -->
<use href="#icon"/>
<!-- Legacy SVG 1.1: use xlink:href -->
<use xlink:href="#icon"/>
</svg>
<!-- In HTML5 inline SVG, the xmlns is optional -->
<!-- But xlink namespace is needed if you use xlink:href -->Comments & Metadata
<title> and <desc> provide accessibility text read by screen readers — always include them for meaningful SVGs. <metadata> holds structured data (RDF, Dublin Core) for tooling. Comments use the same <!-- --> syntax as HTML.
<svg width="100" height="100" viewBox="0 0 100 100">
<!-- This is an XML comment (same as HTML) -->
<!-- Metadata is hidden from rendering -->
<metadata>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<dc:title xmlns:dc="http://purl.org/dc/elements/1.1/">My Icon</dc:title>
</rdf:RDF>
</metadata>
<!-- title and desc for accessibility -->
<title>Shopping Cart Icon</title>
<desc>A red shopping cart with 3 items</desc>
<circle cx="50" cy="50" r="40" fill="red"/>
</svg>Shapes
Rectangle (rect)
rect requires x, y, width, height. rx/ry create rounded corners (if only rx is set, ry defaults to rx). fill='none' makes the interior transparent. stroke-width is centered on the edge, so half extends outside the geometry.
<svg width="200" height="100" viewBox="0 0 200 100">
<!-- Basic rectangle -->
<rect x="10" y="10" width="80" height="50" fill="steelblue"/>
<!-- Rounded corners -->
<rect x="110" y="10" width="80" height="50"
rx="10" ry="10" fill="tomato"/>
<!-- Stroked, no fill -->
<rect x="10" y="70" width="80" height="20"
fill="none" stroke="black" stroke-width="2"/>
</svg>Circle & Ellipse
circle is defined by center (cx, cy) and radius r. ellipse has separate rx and ry for stretching. Both default cx/cy to 0 if omitted. Unlike rect, circles/ellipses cannot have rounded corners — they're already curved.
<svg width="200" height="120" viewBox="0 0 200 120">
<!-- circle: cx, cy (center), r (radius) -->
<circle cx="50" cy="60" r="40" fill="gold"/>
<!-- ellipse: cx, cy, rx (x radius), ry (y radius) -->
<ellipse cx="150" cy="60" rx="50" ry="30" fill="mediumpurple"/>
<!-- Circle as an ellipse (rx == ry) -->
<ellipse cx="100" cy="20" rx="15" ry="15" fill="black"/>
</svg>Line
line connects two points. It has no fill (only stroke). stroke-dasharray creates dashed lines — the pattern is 'dash-length,gap-length' repeated. stroke-linecap (butt, round, square) controls the end caps.
<svg width="200" height="100" viewBox="0 0 200 100">
<!-- line: x1,y1 (start) to x2,y2 (end) -->
<line x1="10" y1="10" x2="190" y2="10" stroke="black" stroke-width="2"/>
<!-- Diagonal line -->
<line x1="10" y1="90" x2="190" y2="10" stroke="red" stroke-width="3"/>
<!-- Dashed line -->
<line x1="10" y1="50" x2="190" y2="50"
stroke="blue" stroke-width="2" stroke-dasharray="10,5"/>
</svg>Polyline & Polygon
polyline draws connected line segments without closing the shape (fill applies as if closed, but no stroke connects the ends). polygon automatically closes by connecting the last point back to the first. points is a space- or comma-separated list of x,y pairs.
<svg width="200" height="120" viewBox="0 0 200 120">
<!-- polyline: open shape (no auto-close) -->
<polyline points="10,10 50,90 100,10 150,90 190,10"
fill="none" stroke="green" stroke-width="2"/>
<!-- polygon: closed shape (auto-connects last to first) -->
<polygon points="100,10 190,110 10,110"
fill="lime" stroke="black" stroke-width="1"/>
<!-- Star (polygon with 10 points) -->
<polygon points="50,5 61,38 95,38 67,58 78,90 50,70 22,90 33,58 5,38 39,38"
fill="gold" stroke="orange"/>
</svg>Common Shape Attributes
fill and stroke are the two main paint properties. opacity affects the entire element; fill-opacity and stroke-opacity act independently. stroke-linecap (butt/round/square) shapes line ends; stroke-linejoin (miter/round/bevel) shapes corners.
<svg width="200" height="100" viewBox="0 0 200 100">
<rect x="10" y="10" width="80" height="80"
fill="#3498db"
fill-opacity="0.5"
stroke="#2c3e50"
stroke-width="3"
stroke-opacity="0.8"
stroke-dasharray="5,3"
stroke-linecap="round"
stroke-linejoin="round"
opacity="0.9"/>
<!-- opacity affects the whole element -->
<!-- fill-opacity/stroke-opacity affect parts independently -->
</svg>Fill Rules (evenodd)
fill-rule determines how overlapping regions of a shape are filled. 'nonzero' (default) fills based on winding direction. 'evenodd' fills regions that are enclosed an odd number of times — this creates the classic 'donut hole' effect for self-intersecting paths and concentric polygons.
<svg width="200" height="100" viewBox="0 0 200 100">
<!-- Default fill-rule: nonzero -->
<polygon points="50,10 90,90 10,90 50,30 80,80 20,80"
fill="red" fill-rule="nonzero"/>
<!-- fill-rule: evenodd creates holes in overlapping shapes -->
<polygon points="150,10 190,90 110,90 150,30 180,80 120,80"
fill="blue" fill-rule="evenodd"/>
</svg>Path
Path Commands Overview
The <path> element is the most powerful SVG shape — its d attribute holds a string of drawing commands. Uppercase commands use absolute coordinates; lowercase use coordinates relative to the current pen position. M, L, C, Q, A, Z are the core commands.
<svg width="200" height="100" viewBox="0 0 200 100">
<!-- The d attribute contains all path commands -->
<path d="M 10 10 L 190 10 L 100 90 Z" fill="none" stroke="black"/>
<!-- Uppercase = absolute, lowercase = relative -->
<!-- M = moveto (lift pen, move) -->
<!-- L = lineto (draw straight line) -->
<!-- H = horizontal lineto, V = vertical lineto -->
<!-- C = cubic bezier, S = smooth cubic -->
<!-- Q = quadratic bezier, T = smooth quadratic -->
<!-- A = arc, Z = closepath -->
</svg>M, L, H, V, Z (Lines)
M moves the pen without drawing. L draws to a point. H/V draw horizontal/vertical lines (one coordinate). Z closes the path back to the start. Lowercase versions (l, h, v, z) interpret coordinates as offsets from the current position, making subpaths reusable.
<svg width="200" height="120" viewBox="0 0 200 120">
<!-- M = moveto, L = lineto, Z = close path -->
<path d="M 10 10 L 190 10 L 190 110 L 10 110 Z"
fill="none" stroke="black"/>
<!-- H = horizontal line, V = vertical line (one coord each) -->
<path d="M 10 60 H 190" stroke="red"/>
<!-- Relative commands (lowercase): coords are offsets -->
<path d="M 10 80 l 30 -20 l 30 20 l 30 -20 l 30 20"
fill="none" stroke="blue"/>
<!-- Multiple M's create subpaths (disconnected segments) -->
<path d="M 10 100 L 50 100 M 100 100 L 150 100" stroke="green"/>
</svg>C & S (Cubic Bezier)
C draws a cubic bezier with two control points that pull the curve. The first control (x1,y1) affects the start tangent; the second (x2,y2) affects the end tangent. S continues smoothly by mirroring the previous curve's second control point — only specify the new second control and endpoint.
<svg width="240" height="120" viewBox="0 0 240 120">
<!-- C = cubic bezier: two control points + endpoint -->
<!-- C x1 y1, x2 y2, x y -->
<path d="M 10 60 C 60 10, 180 110, 230 60"
fill="none" stroke="red" stroke-width="2"/>
<!-- S = smooth cubic: control point mirrors the previous -->
<!-- S x2 y2, x y (first control point is auto-mirrored) -->
<path d="M 10 100 C 60 50, 100 50, 120 100 S 200 150, 230 100"
fill="none" stroke="blue" stroke-width="2"/>
<!-- Multiple cubics in one path -->
<path d="M 10 20 C 40 0, 70 40, 100 20 C 130 0, 160 40, 190 20"
fill="none" stroke="green"/>
</svg>Q & T (Quadratic Bezier)
Q uses one control point instead of two — simpler but less flexible than C. T continues a Q smoothly by mirroring its control point, requiring only the endpoint. Quadratic curves are common in font glyphs and simple icon paths where a single control point suffices.
<svg width="200" height="120" viewBox="0 0 200 120">
<!-- Q = quadratic bezier: one control point + endpoint -->
<!-- Q x1 y1, x y -->
<path d="M 10 100 Q 100 0, 190 100"
fill="none" stroke="purple" stroke-width="2"/>
<!-- T = smooth quadratic: control point auto-mirrored -->
<path d="M 10 60 Q 50 20, 90 60 T 170 60"
fill="none" stroke="orange" stroke-width="2"/>
<!-- Q is simpler than C but less precise — good for curves -->
<!-- with a single bend, like basic arcs and waves -->
</svg>A (Arc)
A draws an elliptical arc. rx, ry are the radii; x-rotation tilts the ellipse. large-arc-flag (0/1) picks the shorter or longer arc between the points. sweep-flag (0/1) picks counterclockwise or clockwise. Arcs are tricky — many designers use C curves instead for predictability.
<svg width="200" height="150" viewBox="0 0 200 150">
<!-- A rx ry, x-rotation, large-arc-flag, sweep-flag, x y -->
<path d="M 10 75 A 60 60, 0, 0, 1, 130 75"
fill="none" stroke="red" stroke-width="2"/>
<!-- large-arc-flag: 0 = small arc, 1 = large arc -->
<!-- sweep-flag: 0 = counterclockwise, 1 = clockwise -->
<!-- Large arc (the long way around) -->
<path d="M 10 75 A 60 60, 0, 1, 1, 130 75"
fill="none" stroke="blue" stroke-width="2"/>
<!-- Elliptical arc (rx != ry) -->
<path d="M 10 120 A 80 30, 0, 0, 0, 170 120"
fill="none" stroke="green" stroke-width="2"/>
</svg>Practical Path Examples
Real-world icons combine M, L, C, and Z to trace shapes. The heart uses cubic beziers for smooth curves. The checkmark is just three points with round line caps. The speech bubble mixes H/V lines for the rectangle and L commands for the tail. Start simple, then refine control points.
<svg width="200" height="120" viewBox="0 0 200 120">
<!-- Heart shape -->
<path d="M 100 30
C 70 0, 20 20, 20 60
C 20 90, 60 110, 100 120
C 140 110, 180 90, 180 60
C 180 20, 130 0, 100 30 Z"
fill="red"/>
<!-- Checkmark -->
<path d="M 20 60 L 50 90 L 100 20"
fill="none" stroke="green" stroke-width="6"
stroke-linecap="round" stroke-linejoin="round"/>
<!-- Speech bubble (combines lines and curves) -->
<path d="M 20 20 H 180 V 80 H 60 L 40 100 L 45 80 H 20 Z"
fill="lightblue" stroke="navy"/>
</svg>Text
Basic Text
The y coordinate refers to the text baseline by default, not the top. text-anchor (start/middle/end) controls horizontal alignment relative to x. dominant-baseline (auto/middle/hanging/...) controls vertical alignment. Both are essential for centering text.
<svg width="200" height="100" viewBox="0 0 200 100">
<text x="10" y="50" font-family="Arial" font-size="24"
fill="black">Hello SVG!</text>
<!-- x, y sets the baseline position (y is the text baseline) -->
<!-- Use dominant-baseline to change the vertical anchor -->
<text x="100" y="50" font-size="20" fill="blue"
text-anchor="middle" dominant-baseline="middle">Centered</text>
</svg>Text Styling
SVG text supports most CSS font properties as attributes. font-family accepts standard font stacks. stroke on text draws an outline — use a small stroke-width to avoid obscuring the fill. letter-spacing and word-spacing work as in CSS.
<svg width="300" height="120" viewBox="0 0 300 120">
<text x="10" y="30"
font-family="Georgia, serif"
font-size="28"
font-weight="bold"
font-style="italic"
fill="darkblue"
stroke="navy"
stroke-width="0.5"
text-decoration="underline"
letter-spacing="2">Styled Text</text>
<!-- font-family uses CSS font stacks -->
<!-- font-weight: normal, bold, 100-900 -->
<!-- font-style: normal, italic, oblique -->
</svg>tspan (Sub-text)
<tspan> lets you style or position parts of text differently — like <span> in HTML. dx/dy offset relative to the previous character; absolute x/y jump to a new position (use x with the original value and dy for line breaks). tspan is how you build multi-line text in SVG.
<svg width="300" height="100" viewBox="0 0 300 100">
<text x="10" y="50" font-size="20" fill="black">
<tspan font-weight="bold" fill="red">Bold red</tspan>
<tspan dx="10" font-style="italic">italic</tspan>
<tspan x="10" dy="30">New line via dy</tspan>
<tspan dx="10" font-size="14">smaller</tspan>
</text>
<!-- dx/dy: relative offset from previous position -->
<!-- x/y: absolute position (useful for line breaks) -->
</svg>Text on a Path
<textPath> renders text along any path element. Reference the path by id via href (modern) or xlink:href (legacy). startOffset (a length or percentage) positions the text along the path. This is the standard technique for curved labels on arcs, circles, or custom shapes.
<svg width="300" height="150" viewBox="0 0 300 150">
<!-- Define a path (can be invisible) -->
<defs>
<path id="curve" d="M 20 100 Q 150 0, 280 100" fill="none"/>
</defs>
<!-- Text follows the path via xlink:href / href -->
<text font-size="20" fill="purple">
<textPath href="#curve">Text along a curved path!</textPath>
</text>
<!-- startOffset shifts where the text begins on the path -->
<text font-size="14" fill="gray">
<textPath href="#curve" startOffset="50%" text-anchor="middle">
Centered on path
</textPath>
</text>
</svg>Writing Mode & Direction
writing-mode='tb' makes text flow top-to-bottom (common in East Asian typography). direction='rtl' reverses character order for right-to-left scripts. For arbitrary rotation, use the transform attribute — rotate(angle, cx, cy) rotates around point (cx, cy).
<svg width="200" height="200" viewBox="0 0 200 200">
<!-- Vertical text (top-to-bottom) -->
<text x="50" y="20" writing-mode="tb" font-size="20">Vertical</text>
<!-- Right-to-left text -->
<text x="190" y="100" direction="rtl" font-size="20"
text-anchor="end">RTL text</text>
<!-- Rotated text (via transform) -->
<text x="100" y="100" font-size="20"
transform="rotate(45, 100, 100)">Rotated 45°</text>
</svg>Text Accessibility
SVG text is selectable and accessible by default, but decorative SVG should have <title> and <desc>. For inline SVG used as UI elements, add role='img' and aria-label so screen readers announce the meaning. Avoid putting essential information only in SVG text — HTML text is more accessible.
<svg width="200" height="80" viewBox="0 0 200 80">
<title>Error Icon</title>
<desc>A red circle with a white exclamation mark</desc>
<!-- role and aria-label for screen readers -->
<text role="img" aria-label="Error: 3 items need attention"
x="100" y="45" text-anchor="middle"
font-size="24" fill="red">! Error</text>
</svg>Gradients
Linear Gradient
linearGradient transitions colors along a line. x1/y1 to x2/y2 define the direction (0% 0% to 100% 0% is left-to-right). <stop> elements define color points; offset is 0-100% or 0-1. Reference the gradient with fill='url(#id)'. Gradients must live in <defs>.
<svg width="200" height="100" viewBox="0 0 200 100">
<defs>
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="red"/>
<stop offset="50%" stop-color="yellow"/>
<stop offset="100%" stop-color="green"/>
</linearGradient>
</defs>
<rect x="10" y="10" width="180" height="80" fill="url(#grad1)"/>
</svg>Radial Gradient
radialGradient radiates from a center point. cx/cy is the center, r is the radius. fx/fy is the focal point (where the 0% color starts) — offsetting it creates a directional light effect. Defaults: cx/cy at 50%, r at 50%, fx/fy mirroring cx/cy.
<svg width="200" height="200" viewBox="0 0 200 200">
<defs>
<radialGradient id="grad2" cx="50%" cy="50%" r="50%" fx="50%" fy="50%">
<stop offset="0%" stop-color="white"/>
<stop offset="50%" stop-color="orange"/>
<stop offset="100%" stop-color="darkred"/>
</radialGradient>
</defs>
<circle cx="100" cy="100" r="90" fill="url(#grad2)"/>
</svg>Gradient Units & Spread
gradientUnits='objectBoundingBox' (default) makes coordinates relative to each shape (0-1), so the same gradient fits any element. 'userSpaceOnUse' uses absolute SVG coordinates, so the gradient is positioned in the document — useful for aligning gradients across multiple shapes.
<svg width="300" height="100" viewBox="0 0 300 100">
<defs>
<!-- objectBoundingBox (default): coords are 0-1 relative to the shape -->
<linearGradient id="g1" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stop-color="blue"/>
<stop offset="1" stop-color="cyan"/>
</linearGradient>
<!-- userSpaceOnUse: coords are in the SVG's user units -->
<linearGradient id="g2" x1="0" y1="0" x2="200" y2="0"
gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="red"/>
<stop offset="1" stop-color="yellow"/>
</linearGradient>
<!-- spreadMethod: pad (default), reflect, repeat -->
<linearGradient id="g3" x1="0" y1="0" x2="0.3" y2="0"
spreadMethod="repeat">
<stop offset="0" stop-color="green"/>
<stop offset="1" stop-color="lime"/>
</linearGradient>
</defs>
<rect width="100" height="100" x="0" fill="url(#g1)"/>
<rect width="100" height="100" x="100" fill="url(#g2)"/>
<rect width="100" height="100" x="200" fill="url(#g3)"/>
</svg>Stop Opacity & Multiple Stops
Each stop can have stop-opacity (0-1) for transparency transitions. Combine many stops for complex color ramps. This sunset gradient fades from transparent blue at the top to opaque dark red at the bottom — a common technique for atmospheric backgrounds.
<svg width="200" height="200" viewBox="0 0 200 200">
<defs>
<linearGradient id="sunset" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="deepskyblue" stop-opacity="0"/>
<stop offset="40%" stop-color="orange" stop-opacity="0.8"/>
<stop offset="70%" stop-color="orangered"/>
<stop offset="100%" stop-color="darkred"/>
</linearGradient>
</defs>
<rect width="200" height="200" fill="url(#sunset)"/>
</svg>Reusing Gradients
Define a gradient once in <defs> and reference it on any number of shapes via fill='url(#id)'. Use href to derive a new gradient from an existing one, overriding only some attributes (like direction). This keeps your SVG DRY and reduces file size.
<svg width="300" height="100" viewBox="0 0 300 100">
<defs>
<!-- A reusable gradient -->
<linearGradient id="metal" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#eee"/>
<stop offset="50%" stop-color="#999"/>
<stop offset="100%" stop-color="#333"/>
</linearGradient>
<!-- Gradient referencing another via href -->
<linearGradient id="metal-horizontal" href="#metal"
x1="0" y1="0" x2="1" y2="0"/>
</defs>
<!-- Same gradient on multiple shapes -->
<rect x="10" y="10" width="80" height="80" fill="url(#metal)"/>
<circle cx="150" cy="50" r="40" fill="url(#metal)"/>
<rect x="210" y="10" width="80" height="80" fill="url(#metal-horizontal)"/>
</svg>Patterns
Basic Pattern
A <pattern> tiles a repeating graphic. The pattern's children define one tile; width/height set the tile size. patternUnits='userSpaceOnUse' (recommended) means the tile dimensions are in SVG user units, independent of the shape being filled. Reference with fill='url(#id)'.
<svg width="200" height="200" viewBox="0 0 200 200">
<defs>
<pattern id="dots" x="0" y="0" width="20" height="20"
patternUnits="userSpaceOnUse">
<circle cx="10" cy="10" r="3" fill="steelblue"/>
</pattern>
</defs>
<rect width="200" height="200" fill="url(#dots)"/>
</svg>Pattern Units
patternUnits='objectBoundingBox' (default) sizes tiles as a fraction of the shape — the pattern scales with each shape. 'userSpaceOnUse' uses absolute units, so the pattern is consistent across shapes regardless of size. Most real-world patterns use userSpaceOnUse.
<svg width="300" height="100" viewBox="0 0 300 100">
<defs>
<!-- objectBoundingBox (default): tile size is relative to shape -->
<!-- 0.1 = 10% of the shape's width/height -->
<pattern id="p1" width="0.1" height="0.1">
<rect width="10" height="10" fill="red" opacity="0.5"/>
</pattern>
<!-- userSpaceOnUse: tile size is in SVG units -->
<pattern id="p2" width="20" height="20"
patternUnits="userSpaceOnUse">
<rect width="10" height="10" fill="blue" opacity="0.5"/>
</pattern>
</defs>
<rect width="150" height="100" fill="url(#p1)"/>
<rect x="150" width="150" height="100" fill="url(#p2)"/>
</svg>Stripes & Checkerboard
patternTransform rotates, scales, or skews the entire pattern. For stripes, fill half the tile with one color and the other half with another, then rotate. The checkerboard uses four rects (or two L-shaped pairs) per tile. These patterns are common in diagrams and textures.
<svg width="200" height="200" viewBox="0 0 200 200">
<defs>
<!-- Diagonal stripes -->
<pattern id="stripes" width="20" height="20"
patternUnits="userSpaceOnUse"
patternTransform="rotate(45)">
<rect width="20" height="10" fill="gold"/>
<rect y="10" width="20" height="10" fill="black"/>
</pattern>
<!-- Checkerboard -->
<pattern id="checker" width="40" height="40"
patternUnits="userSpaceOnUse">
<rect width="40" height="40" fill="white"/>
<rect width="20" height="20" fill="black"/>
<rect x="20" y="20" width="20" height="20" fill="black"/>
</pattern>
</defs>
<rect width="100" height="200" fill="url(#stripes)"/>
<rect x="100" width="100" height="200" fill="url(#checker)"/>
</svg>Pattern Content Units
patternContentUnits controls the coordinate system for the pattern's child elements (not the tile size). 'objectBoundingBox' makes children's coordinates fractions of the shape — useful when you want pattern detail to scale proportionally with each shape. Rare but powerful.
<svg width="200" height="200" viewBox="0 0 200 200">
<defs>
<!-- patternContentUnits: how the pattern's children are measured -->
<!-- userSpaceOnUse (default): children use SVG units -->
<!-- objectBoundingBox: children are relative to the shape -->
<pattern id="pb" width="0.25" height="0.25"
patternContentUnits="objectBoundingBox">
<circle cx="0.125" cy="0.125" r="0.05" fill="green"/>
</pattern>
</defs>
<!-- The dots scale with each shape's bounding box -->
<rect width="100" height="100" fill="url(#pb)"/>
<rect x="100" width="100" height="200" fill="url(#pb)"/>
</svg>Nested Patterns
Patterns can contain gradients, other patterns, or any SVG content. This grid pattern fills each cell with a subtle gradient and draws grid lines on top. Nesting lets you build complex textures. Keep tile sizes reasonable to avoid performance issues with very small tiles.
<svg width="200" height="200" viewBox="0 0 200 200">
<defs>
<!-- A pattern can reference another pattern or gradient -->
<pattern id="grid" width="40" height="40"
patternUnits="userSpaceOnUse">
<rect width="40" height="40" fill="url(#cellGrad)"/>
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="#ccc"/>
</pattern>
<linearGradient id="cellGrad">
<stop offset="0" stop-color="#f0f8ff"/>
<stop offset="1" stop-color="#e0e0ff"/>
</linearGradient>
</defs>
<rect width="200" height="200" fill="url(#grid)"/>
</svg>Transform
Translate
translate(tx, ty) moves an element by tx, ty. If ty is omitted, it defaults to 0. The transform applies to the element's coordinate system, so x/y attributes are relative to the translated origin. CSS transforms (via style) work on inline SVG in modern browsers and can be animated with CSS transitions.
<svg width="200" height="100" viewBox="0 0 200 100">
<!-- Original -->
<rect x="10" y="10" width="40" height="40" fill="red"/>
<!-- Translated 80px right -->
<rect x="10" y="10" width="40" height="40" fill="blue"
transform="translate(80, 0)"/>
<!-- translate with one arg moves only horizontally -->
<rect x="10" y="10" width="40" height="40" fill="green"
transform="translate(0, 50)"/>
<!-- Equivalent in CSS (for inline SVG in HTML) -->
<rect x="10" y="10" width="40" height="40" fill="orange"
style="transform: translate(150px, 0);"/>
</svg>Rotate
rotate(angle, cx, cy) rotates by angle degrees around point (cx, cy). Without cx/cy, it rotates around the origin (0,0), which often isn't what you want. Angles are clockwise (positive) or counterclockwise (negative). Always specify the center to rotate an element in place.
<svg width="200" height="200" viewBox="0 0 200 200">
<rect x="50" y="50" width="100" height="20" fill="red"/>
<!-- rotate(angle) around origin (0,0) -->
<rect x="50" y="50" width="100" height="20" fill="blue"
transform="rotate(45)"/>
<!-- rotate(angle, cx, cy) around point (cx, cy) -->
<rect x="50" y="50" width="100" height="20" fill="green"
transform="rotate(45, 100, 60)"/>
<!-- Negative angles rotate counterclockwise -->
<rect x="50" y="50" width="100" height="20" fill="orange"
transform="rotate(-30, 100, 60)"/>
</svg>Scale & Skew
scale(sx, sy) multiplies dimensions; one argument scales both equally. Scaling also affects stroke-width and positions. skewX/skewY shear the element along an axis by an angle. All transforms are relative to the origin (0,0), so scaling a shape at x=100 also moves it.
<svg width="200" height="200" viewBox="0 0 200 200">
<!-- Original square -->
<rect x="10" y="10" width="50" height="50" fill="red"/>
<!-- scale(2): 2x in both dimensions -->
<rect x="10" y="10" width="50" height="50" fill="blue"
transform="scale(2)"/>
<!-- scale(sx, sy): different x and y scaling -->
<rect x="10" y="10" width="50" height="50" fill="green"
transform="scale(1, 2)"/>
<!-- skewX / skewY: shear the shape -->
<rect x="10" y="10" width="50" height="50" fill="orange"
transform="skewX(30)"/>
</svg>Matrix Transform
The matrix(a,b,c,d,e,f) is the underlying form of all transforms. a,d scale; b,c skew; e,f translate. Most developers use the named transforms (translate, rotate, etc.) for readability, but matrix is what tools output and is useful for combining transforms or parsing.
<svg width="200" height="200" viewBox="0 0 200 200">
<!-- transform="matrix(a, b, c, d, e, f)" -->
<!-- Represents: [a c e] [b d f] [0 0 1] -->
<!-- New x = a*x + c*y + e -->
<!-- New y = b*x + d*y + f -->
<!-- Identity (no change) -->
<rect width="50" height="50" fill="red" transform="matrix(1,0,0,1,0,0)"/>
<!-- Translate (100, 50) -->
<rect width="50" height="50" fill="blue" transform="matrix(1,0,0,1,100,50)"/>
<!-- Scale 2x and translate -->
<rect width="50" height="50" fill="green" transform="matrix(2,0,0,2,50,0)"/>
</svg>Combining Transforms
Multiple transforms in the attribute apply right-to-left (the rightmost happens first). This means 'translate(100,100) rotate(45)' rotates the shape around its own origin first, then moves it — usually what you want. CSS transforms with transform-origin are often more intuitive for inline SVG.
<svg width="200" height="200" viewBox="0 0 200 200">
<!-- Multiple transforms apply right-to-left -->
<rect x="0" y="0" width="40" height="40" fill="red"
transform="translate(100, 100) rotate(45) scale(2)"/>
<!-- This means: scale, then rotate, then translate -->
<!-- Order matters! rotate then translate != translate then rotate -->
<!-- transform-origin via CSS (for inline SVG) -->
<rect x="80" y="80" width="40" height="40" fill="blue"
style="transform: rotate(45deg); transform-origin: center;"/>
<!-- transform-origin: center, 50% 50%, or explicit coords -->
</svg>transform-origin (CSS)
When animating SVG with CSS, set transform-origin explicitly (in SVG user units) and transform-box (fill-box for the element's bounding box, view-box for the SVG canvas). Without these, rotation centers on the SVG's origin, not the element. CSS animations and transitions work on inline SVG.
<svg width="200" height="200" viewBox="0 0 200 200">
<style>
.gear {
transform-origin: 100px 100px; /* SVG user units */
transform-box: fill-box; /* or view-box */
animation: spin 4s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
<circle class="gear" cx="100" cy="100" r="50" fill="orange"/>
<circle cx="100" cy="100" r="10" fill="black"/>
</svg>Group (g)
Basic Grouping
<g> groups elements so they share attributes (fill, stroke, transform, etc.) and can be manipulated as a unit. Children inherit presentation attributes from the group. Groups are essential for organizing complex SVGs and applying transforms to multiple shapes at once.
<svg width="200" height="200" viewBox="0 0 200 200">
<g fill="steelblue" stroke="navy" stroke-width="2">
<!-- Children inherit the group's attributes -->
<circle cx="60" cy="60" r="30"/>
<rect x="100" y="30" width="60" height="60" rx="5"/>
<path d="M 30 150 L 100 110 L 170 150 Z"/>
</g>
<!-- Without g, you'd repeat fill/stroke on each element -->
</svg>Group Transforms
A transform on <g> creates a new coordinate system for its children. This is the key to building reusable components: design around (0,0), then position the whole group with translate. Children's coordinates are relative to the group's transformed origin.
<svg width="200" height="200" viewBox="0 0 200 200">
<!-- A transform on a group applies to all children -->
<g transform="translate(100, 100) rotate(45)">
<rect x="-30" y="-30" width="60" height="60" fill="red"/>
<circle cx="0" cy="0" r="15" fill="white"/>
</g>
<!-- The group defines its own coordinate system -->
<!-- Children use coords relative to the group's origin -->
<g transform="translate(50, 150)">
<text x="0" y="0">Origin here</text>
<circle cx="0" cy="-20" r="5"/>
</g>
</svg>Nested Groups
Groups can nest arbitrarily deep. Each nested transform compounds — the innermost element is affected by all ancestor transforms in order. This is how you build hierarchical scenes (e.g. a character with rotating limbs, each limb with moving hands). Keep nesting reasonable for performance.
<svg width="200" height="200" viewBox="0 0 200 200">
<g transform="translate(100, 100)"> <!-- outer group -->
<g transform="scale(0.5)"> <!-- inner group -->
<circle r="80" fill="blue"/>
<g transform="translate(40, 0)"> <!-- innermost -->
<circle r="20" fill="white"/>
</g>
</g>
</g>
<!-- Transforms compound: the innermost circle is -->
<!-- translated, scaled, and translated again -->
</svg>Group IDs & References
Give a group an id and reference it with <use href='#id'> to duplicate it. This is the basis of SVG sprites and reusable components. The group can live in <defs> (not rendered directly) and be instantiated many times with different transforms. Overriding attributes on <use> is limited.
<svg width="200" height="100" viewBox="0 0 200 100">
<defs>
<g id="star">
<polygon points="50,5 61,38 95,38 67,58 78,90 50,70 22,90 33,58 5,38 39,38"
fill="gold" stroke="orange"/>
</g>
</defs>
<!-- Reference the group with <use> -->
<use href="#star" transform="translate(0, 0) scale(0.5)"/>
<use href="#star" transform="translate(100, 0) scale(0.5)"/>
<use href="#star" transform="translate(0, 50) scale(0.3)"/>
</svg>Conditional Groups
Groups with ids are perfect hook points for CSS (hover, active states) and JavaScript event handling. Treat a <g> like a component: bundle its shapes, give it an id, and interact with it as a unit. Event listeners on a group catch events from all its children.
<svg width="200" height="100" viewBox="0 0 200 100">
<!-- <g> can have ids for CSS targeting and JS selection -->
<g id="button" class="interactive" data-action="save">
<rect width="80" height="30" rx="5" fill="steelblue"/>
<text x="40" y="20" text-anchor="middle" fill="white">Save</text>
</g>
<style>
#button:hover rect { fill: royalblue; }
#button { cursor: pointer; }
</style>
<script>
document.getElementById("button").addEventListener("click", save);
</script>
</svg>Group vs Symbol
<symbol> is purpose-built for reusable icons: it's never rendered directly and has its own viewBox, so <use> can size it independently. <g> in <defs> also works but lacks the viewBox feature. For icon systems, prefer <symbol> — it handles scaling and aspect ratio automatically.
<svg width="0" height="0" viewBox="0 0 0 0">
<!-- <g> in <defs>: original is hidden, use can instantiate -->
<defs>
<g id="icon-g">
<rect width="20" height="20" fill="red"/>
</g>
</defs>
<!-- <symbol> is like <g> but supports viewBox and is never rendered -->
<symbol id="icon-s" viewBox="0 0 20 20">
<rect width="20" height="20" fill="blue"/>
</symbol>
<!-- Use with explicit size (symbol scales to fit) -->
<use href="#icon-g" x="0" y="0"/>
<use href="#icon-s" x="40" y="0" width="40" height="40"/>
</svg>Use / Symbol / Defs
defs Element
<defs> holds reusable definitions that aren't drawn until referenced: gradients, patterns, filters, clip paths, masks, symbols, and groups. Putting them in defs keeps the rendered output clean and signals intent. Anything outside defs renders immediately.
<svg width="200" height="100" viewBox="0 0 200 100">
<defs>
<!-- Anything in defs is defined but NOT rendered -->
<linearGradient id="myGrad">
<stop offset="0" stop-color="red"/>
<stop offset="1" stop-color="blue"/>
</linearGradient>
<pattern id="myPattern" width="20" height="20">
<circle cx="10" cy="10" r="5" fill="url(#myGrad)"/>
</pattern>
<g id="myShape">
<rect width="40" height="40" rx="5"/>
</g>
</defs>
<!-- Reference and render them -->
<rect width="100" height="100" fill="url(#myPattern)"/>
<use href="#myShape" x="120" y="30" fill="green"/>
</svg>use Element
<use> clones and renders a defined element (g, symbol, or any shape). x/y position the instance. fill/stroke on <use> override the original only if the original didn't specify them (CSS inheritance). transform applies to the instance. This is how SVG sprites work.
<svg width="300" height="100" viewBox="0 0 300 100">
<defs>
<g id="cloud">
<circle cx="30" cy="50" r="20" fill="white"/>
<circle cx="50" cy="45" r="25" fill="white"/>
<circle cx="75" cy="50" r="20" fill="white"/>
</g>
</defs>
<!-- Basic use -->
<use href="#cloud"/>
<!-- Positioned and styled -->
<use href="#cloud" x="120" y="0" fill="lightgray"/>
<use href="#cloud" x="200" y="10" transform="scale(0.5)" fill="silver"/>
</svg>symbol Element
<symbol> defines a reusable graphic with its own viewBox. Unlike <g>, it's never rendered directly and scales to fit the <use> element's dimensions. fill='currentColor' lets the consumer set color via CSS color. This is the standard pattern for SVG icon systems.
<!-- In a hidden sprite file or <defs> -->
<svg width="0" height="0" style="position:absolute">
<symbol id="heart" viewBox="0 0 32 32">
<path d="M16 28 C 4 18, 4 8, 12 8 C 14 8, 16 10, 16 12
C 16 10, 18 8, 20 8 C 28 8, 28 18, 16 28 Z"
fill="currentColor"/>
</symbol>
<symbol id="star" viewBox="0 0 32 32">
<polygon points="16,2 20,12 31,12 22,19 25,30 16,23 7,30 10,19 1,12 12,12"
fill="currentColor"/>
</symbol>
</svg>
<!-- Usage: size with width/height, color with CSS -->
<svg width="24" height="24"><use href="#heart"/></svg>
<svg width="48" height="48"><use href="#star" style="color: gold"/></svg>External SVG References
<use> can reference symbols in external SVG files via href='file.svg#id'. However, browser support is inconsistent (no IE, CORS restrictions), and it breaks with file://. The reliable approach is to inline a hidden SVG sprite at the top of your HTML and reference symbols within the same document.
<!-- Reference a symbol in an external .svg file -->
<svg width="24" height="24">
<use href="icons.svg#heart"/>
</svg>
<!-- The external file (icons.svg) would contain: -->
<!-- <svg xmlns="http://www.w3.org/2000/svg" style="display:none"> -->
<!-- <symbol id="heart" viewBox="0 0 32 32">...</symbol> -->
<!-- </svg> -->
<!-- Note: external references have limited browser support -->
<!-- and don't work with file:// protocol. Inline is more reliable. -->
<!-- For production, inline the sprite at the top of your HTML body. -->Overriding Use Attributes
When the original shape omits a presentation attribute (like fill), <use> can set it — the instance inherits the value. If the original specifies fill, the use's fill is ignored (presentation attributes beat CSS inheritance). To make customizable symbols, omit the attributes you want to override.
<svg width="300" height="100" viewBox="0 0 300 100">
<defs>
<!-- Shape without explicit fill — inherits from use -->
<circle id="ball" cx="20" cy="20" r="15"/>
</defs>
<!-- Each use can set its own fill -->
<use href="#ball" fill="red"/>
<use href="#ball" x="50" fill="green"/>
<use href="#ball" x="100" fill="blue" stroke="black" stroke-width="2"/>
</svg>Symbol Nesting & Sprite
This is the production pattern for SVG icons: one hidden sprite SVG at the top of the document containing all icons as <symbol>s, then <use> to render them anywhere. currentColor makes icons inherit text color. CSS controls size. The sprite caches after first load, fast on every page.
<!-- A complete inline SVG sprite system -->
<svg style="display:none" aria-hidden="true">
<symbol id="icon-home" viewBox="0 0 24 24">
<path d="M12 3 L2 12 H5 V21 H10 V14 H14 V21 H19 V12 H22 Z"/>
</symbol>
<symbol id="icon-search" viewBox="0 0 24 24">
<circle cx="11" cy="11" r="7" fill="none" stroke="currentColor" stroke-width="2"/>
<line x1="16" y1="16" x2="21" y2="21" stroke="currentColor" stroke-width="2"/>
</symbol>
</svg>
<!-- Reference anywhere in HTML -->
<svg class="icon"><use href="#icon-home"/></svg>
<svg class="icon"><use href="#icon-search"/></svg>
<style>
.icon { width: 24px; height: 24px; fill: currentColor; }
</style>Filters
Filter Basics
A <filter> contains one or more filter primitives (fe* elements) that process the graphic. in='SourceGraphic' takes the element's own rendering. Apply with filter='url(#id)'. Filters can be expensive — use sparingly on small elements, not large backgrounds.
<svg width="200" height="120" viewBox="0 0 200 120">
<defs>
<filter id="blur1">
<!-- feGaussianBlur: stdDeviation is the blur radius -->
<feGaussianBlur in="SourceGraphic" stdDeviation="3"/>
</filter>
</defs>
<!-- Without filter -->
<text x="10" y="30" font-size="24" fill="red">Sharp</text>
<!-- With filter -->
<text x="10" y="70" font-size="24" fill="red"
filter="url(#blur1)">Blurred</text>
</svg>Drop Shadow
A drop shadow combines feOffset (move), feGaussianBlur (soften), feFlood (color), feComposite (apply color to shadow), and feMerge (layer shadow beneath original). The filter's x/y/width/height (default -10% to 110%) must be enlarged or the shadow gets clipped.
<svg width="200" height="120" viewBox="0 0 200 120">
<defs>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
<!-- Offset the source -->
<feOffset in="SourceAlpha" dx="4" dy="4" result="offset"/>
<!-- Blur the offset copy -->
<feGaussianBlur in="offset" stdDeviation="3" result="blur"/>
<!-- Color the shadow -->
<feFlood flood-color="black" flood-opacity="0.5" result="color"/>
<feComposite in="color" in2="blur" operator="in" result="shadow"/>
<!-- Put shadow under the original -->
<feMerge>
<feMergeNode in="shadow"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<rect x="20" y="20" width="80" height="60" fill="white"
filter="url(#shadow)"/>
</svg>Simple Drop Shadow (CSS)
For simple shadows on inline SVG, the CSS filter: drop-shadow() is far simpler than building an SVG filter. It respects the shape's actual outline (not just the bounding box like box-shadow). Use SVG filters only for complex multi-step effects that CSS can't express.
<!-- Modern browsers: use CSS filter instead of SVG filter -->
<svg width="200" height="100" viewBox="0 0 200 100">
<rect x="50" y="20" width="100" height="60" fill="white"
style="filter: drop-shadow(4px 4px 4px rgba(0,0,0,0.5));"/>
</svg>
<!-- In CSS -->
<style>
.shadowed { filter: drop-shadow(0 0 10px rgba(0,0,0,0.3)); }
</style>
<!-- CSS drop-shadow follows the shape's alpha, unlike box-shadow -->
<!-- which only shadows the bounding box -->Color Matrix
feColorMatrix transforms colors via a 5x4 matrix (RGBA in, RGBA out). type='saturate' with values 0-1 desaturates; type='hueRotate' rotates hue in degrees; type='luminanceToAlpha' converts brightness to alpha. Useful for grayscale, sepia, and color adjustments on SVG content.
<svg width="200" height="100" viewBox="0 0 200 100">
<defs>
<!-- Convert to grayscale -->
<filter id="grayscale">
<feColorMatrix type="matrix"
values="0.33 0.33 0.33 0 0
0.33 0.33 0.33 0 0
0.33 0.33 0.33 0 0
0 0 0 1 0"/>
</filter>
<!-- Built-in saturation -->
<filter id="desaturate">
<feColorMatrix type="saturate" values="0"/>
</filter>
</defs>
<image href="photo.jpg" width="100" height="100" filter="url(#grayscale)"/>
<image href="photo.jpg" x="100" width="100" height="100" filter="url(#desaturate)"/>
</svg>Lighting Effects
feSpecularLighting and feDiffuseLighting simulate 3D lighting on a 2D shape's height map (derived from alpha). surfaceScale controls depth; the light element (fePointLight, feDistantLight, feSpotLight) sets direction. Combined with feComposite, you get emboss, bevel, and 3D text effects.
<svg width="200" height="120" viewBox="0 0 200 120">
<defs>
<filter id="emboss">
<!-- Create a height map from the alpha -->
<feGaussianBlur in="SourceAlpha" stdDeviation="2" result="blur"/>
<!-- Lighting from upper-left -->
<feSpecularLighting in="blur" surfaceScale="5"
specularConstant="0.8" specularExponent="20"
lighting-color="white" result="spec">
<fePointLight x="-50" y="-50" z="200"/>
</feSpecularLighting>
<feComposite in="spec" in2="SourceGraphic"
operator="in" result="lit"/>
<feComposite in="SourceGraphic" in2="lit" operator="arithmetic"
k1="0" k2="1" k3="1" k4="0"/>
</filter>
</defs>
<text x="10" y="70" font-size="48" font-weight="bold"
fill="gray" filter="url(#emboss)">3D</text>
</svg>Turbulence (Texture)
feTurbulence generates procedural noise (fractal or turbulence type) — useful for textures like static, clouds, water, or paper grain. baseFrequency controls scale (0.5 = fine, 0.05 = cloudy). numOctaves adds detail at the cost of speed. Combine with feColorMatrix or feDisplacementMap for effects.
<svg width="200" height="200" viewBox="0 0 200 200">
<defs>
<!-- feTurbulence generates Perlin noise -->
<filter id="noise" x="0" y="0" width="100%" height="100%">
<feTurbulence type="fractalNoise" baseFrequency="0.65"
numOctaves="3" stitchTiles="stitch"/>
<feColorMatrix type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
</filter>
</defs>
<rect width="200" height="200" fill="steelblue"/>
<rect width="200" height="200" filter="url(#noise)"/>
<!-- type: "fractalNoise" or "turbulence" -->
<!-- baseFrequency: lower = larger blobs, higher = finer grain -->
<!-- numOctaves: more = more detail (slower) -->
</svg>Clipping & Masking
Clip Path
clipPath defines a hard-edged boundary — pixels inside are visible, outside are hidden. The clip shape itself isn't drawn. clip-path references it via url(#id). Clipping is binary (in or out); for soft transitions, use masks. Text as a clip path creates the classic 'image inside text' effect.
<svg width="200" height="200" viewBox="0 0 200 200">
<defs>
<!-- A clip path: only the inside is visible -->
<clipPath id="circleClip">
<circle cx="100" cy="100" r="80"/>
</clipPath>
</defs>
<!-- The image is clipped to the circle -->
<rect width="200" height="200" fill="steelblue"
clip-path="url(#circleClip)"/>
<!-- Any shape can be a clip path -->
<clipPath id="textClip">
<text x="100" y="120" font-size="80" text-anchor="middle"
font-weight="bold">SVG</text>
</clipPath>
<rect width="200" height="200" fill="gold" clip-path="url(#textClip)"/>
</svg>Mask
Unlike clip-path (binary), masks support alpha — white areas are fully visible, black areas are hidden, grays are partially transparent. Use a gradient as the mask content for smooth fades. Masks are heavier than clip paths but essential for soft transitions and photo effects.
<svg width="200" height="200" viewBox="0 0 200 200">
<defs>
<!-- A mask: white = visible, black = hidden, gray = partial -->
<mask id="fadeMask">
<linearGradient id="fadeGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="white"/>
<stop offset="1" stop-color="black"/>
</linearGradient>
<rect width="200" height="200" fill="url(#fadeGrad)"/>
</mask>
</defs>
<!-- The image fades from top to bottom -->
<rect width="200" height="200" fill="red" mask="url(#fadeMask)"/>
</svg>Clip Path Units
clipPathUnits='objectBoundingBox' makes the clip path scale to each element's bounding box (coords 0-1), so one clip path works for differently-sized shapes. 'userSpaceOnUse' (default) uses absolute SVG coordinates. Most reusable clips use objectBoundingBox.
<svg width="300" height="100" viewBox="0 0 300 100">
<defs>
<!-- userSpaceOnUse (default): clip coords in SVG units -->
<clipPath id="clip1" clipPathUnits="userSpaceOnUse">
<rect x="10" y="10" width="50" height="50"/>
</clipPath>
<!-- objectBoundingBox: coords are 0-1 relative to the element -->
<clipPath id="clip2" clipPathUnits="objectBoundingBox">
<rect x="0.1" y="0.1" width="0.8" height="0.8"/>
</clipPath>
</defs>
<rect width="100" height="100" fill="red" clip-path="url(#clip1)"/>
<rect x="100" width="100" height="80" fill="blue" clip-path="url(#clip2)"/>
<rect x="200" width="100" height="60" fill="green" clip-path="url(#clip2)"/>
</svg>CSS clip-path
CSS clip-path supports basic shapes (circle, ellipse, polygon, inset) directly without SVG, and can reference SVG clipPath elements via url(#id). This works on HTML elements too, not just SVG. CSS masking is also supported in modern browsers via mask-image.
<style>
/* CSS clip-path works on SVG and HTML elements */
.circle {
clip-path: circle(50% at 50% 50%);
}
.triangle {
clip-path: polygon(50% 0%, 100% 100%, 0% 100%);
}
/* Reference an SVG clipPath */
.svg-clip {
clip-path: url(#myClip);
}
</style>
<svg width="200" height="100">
<defs>
<clipPath id="myClip">
<circle cx="50" cy="50" r="40"/>
</clipPath>
</defs>
<rect class="svg-clip" width="200" height="100" fill="purple"/>
</svg>Practical: Image in Text
Clipping a gradient or image to text is a popular design technique. The clipPath contains a <text> element; the clipped rect fills with the gradient, visible only inside the letter shapes. This works with <image> too — put a photo inside text for magazine-style headers.
<svg width="400" height="120" viewBox="0 0 400 120">
<defs>
<clipPath id="textShape">
<text x="200" y="90" font-size="80" font-weight="900"
text-anchor="middle" font-family="Arial">SUMMER</text>
</clipPath>
</defs>
<!-- Background (visible only through the text) -->
<rect width="400" height="120" fill="black"/>
<!-- Image clipped to the text shape -->
<rect width="400" height="120" fill="url(#sunsetGrad)"
clip-path="url(#textShape)"/>
<linearGradient id="sunsetGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#ff6b6b"/>
<stop offset="0.5" stop-color="#feca57"/>
<stop offset="1" stop-color="#ff9ff3"/>
</linearGradient>
</svg>Animation (SMIL)
animate (Basic)
<animate> transitions an attribute over time. attributeName is the target; from/to define the range; dur is the duration; repeatCount='indefinite' loops forever. SMIL animations are declarative (no JS needed) and work in most browsers except IE. The animation lives as a child of the target element.
<svg width="200" height="100" viewBox="0 0 200 100">
<rect x="0" y="40" width="20" height="20" fill="red">
<animate attributeName="x"
from="0" to="180"
dur="2s"
repeatCount="indefinite"/>
</rect>
<!-- The rect moves from x=0 to x=180 over 2 seconds, forever -->
</svg>animateTransform
<animateTransform> animates the transform attribute. type selects the operation (rotate, scale, translate, skewX, skewY). For rotate, from/to are 'angle cx cy' (center point). This is how you spin elements without JavaScript. Only one animateTransform can run per element unless you use additive='sum'.
<svg width="200" height="200" viewBox="0 0 200 200">
<rect x="80" y="80" width="40" height="40" fill="blue">
<animateTransform attributeName="transform"
type="rotate"
from="0 100 100" to="360 100 100"
dur="3s"
repeatCount="indefinite"/>
</rect>
<!-- type can be: rotate, scale, translate, skewX, skewY -->
<!-- For rotate, the values are "angle cx cy" -->
</svg>animateMotion
<animateMotion> moves an element along a path. Reference the path with <mpath href='#id'> or define path directly. rotate='auto' orients the element to face the direction of travel — essential for arrows, cars, or characters. This is the SVG way to do path-based animation without JS.
<svg width="200" height="150" viewBox="0 0 200 150">
<defs>
<path id="motionPath" d="M 10 75 Q 100 0, 190 75" fill="none"/>
</defs>
<!-- A circle follows the path -->
<circle r="10" fill="red">
<animateMotion dur="3s" repeatCount="indefinite">
<mpath href="#motionPath"/>
</animateMotion>
</circle>
<!-- Show the path for reference -->
<use href="#motionPath" stroke="gray" stroke-width="1"/>
<!-- rotate="auto" makes the element face the direction of motion -->
<circle r="5" fill="blue">
<animateMotion dur="3s" repeatCount="indefinite" rotate="auto">
<mpath href="#motionPath"/>
</animateMotion>
</circle>
</svg>Keyframes & Easing
For multi-step animations, use values (semicolon-separated), keyTimes (0-1 fractions), and keySplines (bezier easing per segment). calcMode='spline' enables easing; 'linear' is the default. This mirrors CSS keyframe animation but in pure SVG. The rect bounces back and forth with smooth easing.
<svg width="200" height="100" viewBox="0 0 200 100">
<rect x="0" y="40" width="20" height="20" fill="green">
<animate attributeName="x"
values="0; 80; 180; 80; 0"
keyTimes="0; 0.25; 0.5; 0.75; 1"
keySplines="0.5 0 0.5 1; 0 0 1 1; 0.5 0 0.5 1; 0 0 1 1"
calcMode="spline"
dur="4s"
repeatCount="indefinite"/>
</rect>
<!-- values: the animated values at each keyTime -->
<!-- keyTimes: 0-1 fractions of dur (semicolon-separated) -->
<!-- keySplines: cubic-bezier easing between each pair -->
<!-- calcMode: "discrete", "linear", "paced", or "spline" -->
</svg>begin, end & Triggers
begin can be a time offset (2s), an event (click, mouseenter), or a reference to another animation's event (anim1.end, anim1.begin+1s). fill='freeze' holds the final value after the animation ends (otherwise it snaps back). This enables click-triggered and chained animations without JavaScript.
<svg width="200" height="100" viewBox="0 0 200 100">
<!-- begin: when to start (time or event) -->
<rect x="10" y="10" width="30" height="30" fill="red">
<animate attributeName="width" from="30" to="100"
dur="1s" begin="2s" fill="freeze"/>
</rect>
<!-- begin on a click event -->
<rect x="10" y="60" width="30" height="30" fill="blue" id="trigger">
<animate attributeName="x" from="10" to="150"
dur="1s" begin="click" fill="freeze"/>
</rect>
<!-- begin when another animation ends -->
<rect x="10" y="80" width="30" height="10" fill="green">
<animate attributeName="x" from="10" to="150"
dur="1s" begin="trigger.click+1s" fill="freeze"/>
</rect>
</svg>CSS Animation Alternative
For inline SVG in web pages, CSS animations are often simpler than SMIL and have better tooling. Use transform-box: fill-box so transform-origin: center refers to the element, not the SVG canvas. SMIL remains useful for standalone SVG files (no CSS available) and path-based motion.
<svg width="200" height="100" viewBox="0 0 200 100">
<style>
.pulse {
animation: pulse 1.5s ease-in-out infinite;
transform-origin: center;
transform-box: fill-box;
}
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.2); opacity: 0.7; }
}
</style>
<circle class="pulse" cx="100" cy="50" r="20" fill="red"/>
</svg>
<!-- CSS animations are more maintainable for web pages -->
<!-- SMIL is better for standalone .svg files -->Events
Mouse Events
SVG elements support standard DOM events: click, mouseenter, mouseleave, mousemove, mousedown, mouseup, dblclick. Use addEventListener as you would on HTML elements. 'this' inside a handler refers to the SVG element. Use setAttribute to change properties reactively.
<svg width="200" height="100" viewBox="0 0 200 100">
<rect id="box" x="10" y="10" width="80" height="80" fill="steelblue"/>
<script>
var box = document.getElementById("box");
box.addEventListener("click", function (e) {
alert("Clicked at " + e.clientX + "," + e.clientY);
});
box.addEventListener("mouseenter", function () {
this.setAttribute("fill", "tomato");
});
box.addEventListener("mouseleave", function () {
this.setAttribute("fill", "steelblue");
});
</script>
</svg>Touch Events
SVG supports touchstart, touchmove, touchend. Convert screen coordinates to SVG coordinates using createSVGPoint and getScreenCTM().inverse() — essential when the SVG is scaled or responsive. preventDefault() on touchstart prevents gesture interference like scrolling.
<svg width="200" height="200" viewBox="0 0 200 200">
<circle id="touch" cx="100" cy="100" r="30" fill="purple"/>
<script>
var circle = document.getElementById("touch");
circle.addEventListener("touchstart", function (e) {
e.preventDefault(); // prevent scrolling
this.setAttribute("fill", "orange");
});
circle.addEventListener("touchend", function () {
this.setAttribute("fill", "purple");
});
// touchmove for dragging
circle.addEventListener("touchmove", function (e) {
var touch = e.touches[0];
var pt = svg.createSVGPoint();
pt.x = touch.clientX; pt.y = touch.clientY;
var svgP = pt.matrixTransform(svg.getScreenCTM().inverse());
this.setAttribute("cx", svgP.x);
this.setAttribute("cy", svgP.y);
});
</script>
</svg>Coordinate Conversion
When an SVG has a viewBox different from its pixel size, screen coordinates don't map 1:1 to SVG coordinates. createSVGPoint + getScreenCTM().inverse() converts correctly, accounting for viewBox, CSS scaling, and even transforms on ancestor elements. Essential for accurate click detection.
<svg width="500" height="300" viewBox="0 0 1000 600" id="mysvg">
<rect width="1000" height="600" fill="lightblue"/>
<script>
var svg = document.getElementById("mysvg");
svg.addEventListener("click", function (e) {
// Convert screen (clientX/Y) to SVG user coordinates
var pt = svg.createSVGPoint();
pt.x = e.clientX;
pt.y = e.clientY;
// getScreenCTM maps SVG coords to screen; inverse maps back
var svgPoint = pt.matrixTransform(
svg.getScreenCTM().inverse()
);
console.log("SVG coords:", svgPoint.x, svgPoint.y);
// This accounts for viewBox scaling and CSS sizing
});
</script>
</svg>Event Delegation
Event delegation — one listener on a parent group instead of many on children — is more efficient and handles dynamically added shapes automatically. e.target is the shape that was clicked; e.currentTarget is the element with the listener. Use data-* attributes to store per-shape metadata.
<svg width="300" height="100" viewBox="0 0 300 100">
<g id="shapes">
<circle cx="50" cy="50" r="30" fill="red" data-name="circle1"/>
<rect x="100" y="20" width="60" height="60" fill="blue" data-name="rect1"/>
<polygon points="220,20 280,20 250,80" fill="green" data-name="tri1"/>
</g>
<script>
// One listener on the parent group handles all children
document.getElementById("shapes").addEventListener("click", function (e) {
var target = e.target;
var name = target.getAttribute("data-name");
console.log("Clicked:", name, target.tagName);
// e.target is the actual shape; e.currentTarget is the group
});
</script>
</svg>Drag Interaction
This is the classic SVG drag pattern: mousedown sets a flag, mousemove updates the element's position (using converted SVG coordinates), mouseup clears the flag. Attach mousemove/mouseup to the SVG (not the element) so dragging continues even when the cursor leaves the shape. This is the foundation of SVG-based editors.
<svg width="400" height="300" viewBox="0 0 400 300" id="canvas">
<circle id="draggable" cx="200" cy="150" r="30" fill="orange" cursor="grab"/>
<script>
var circle = document.getElementById("draggable");
var svg = document.getElementById("canvas");
var dragging = false;
function getSVGPoint(e) {
var pt = svg.createSVGPoint();
pt.x = e.clientX; pt.y = e.clientY;
return pt.matrixTransform(svg.getScreenCTM().inverse());
}
circle.addEventListener("mousedown", function () {
dragging = true;
this.setAttribute("cursor", "grabbing");
});
svg.addEventListener("mousemove", function (e) {
if (!dragging) return;
var p = getSVGPoint(e);
circle.setAttribute("cx", p.x);
circle.setAttribute("cy", p.y);
});
svg.addEventListener("mouseup", function () {
dragging = false;
circle.setAttribute("cursor", "grab");
});
</script>
</svg>JavaScript DOM
Creating Elements
SVG elements must be created with document.createElementNS(namespace, tagName) — createElement won't work for SVG (it creates unknown HTML elements). The SVG namespace is 'http://www.w3.org/2000/svg'. Use setAttribute for properties. This is the foundation of dynamic SVG generation.
<svg id="canvas" width="200" height="200" viewBox="0 0 200 200">
<script>
var svg = document.getElementById("canvas");
var NS = "http://www.w3.org/2000/svg";
// createElementNS is required for SVG elements
var circle = document.createElementNS(NS, "circle");
circle.setAttribute("cx", "100");
circle.setAttribute("cy", "100");
circle.setAttribute("r", "50");
circle.setAttribute("fill", "red");
svg.appendChild(circle);
// Create a path
var path = document.createElementNS(NS, "path");
path.setAttribute("d", "M 10 10 L 100 100");
path.setAttribute("stroke", "black");
svg.appendChild(path);
</script>
</svg>Modifying Elements
Use setAttribute/getAttribute for SVG presentation attributes (fill, stroke, width, etc.). classList works reliably across browsers. Setting style.fill works because it maps to the CSS property. Avoid innerHTML for SVG (inconsistent across browsers); build the DOM with createElementNS instead.
<svg width="200" height="100" viewBox="0 0 200 100">
<rect id="r" x="10" y="10" width="50" height="50" fill="blue"/>
<script>
var r = document.getElementById("r");
// Change attributes
r.setAttribute("fill", "red");
r.setAttribute("width", "80");
// Get attribute values
var w = r.getAttribute("width"); // "80"
// Remove attributes
r.removeAttribute("fill");
// Class manipulation (use classList, not className for SVG in some browsers)
r.classList.add("highlight");
r.classList.toggle("active");
// Style (use setAttribute for presentation attributes)
r.style.fill = "green"; // works via CSS
</script>
</svg>Reading Geometry
getBBox() returns the bounding box in SVG user units (ignoring transforms). getBoundingClientRect() returns screen coordinates (after all transforms and CSS). getTotalLength() gives path/line length; getPointAtLength(d) returns the point at distance d — essential for path-based animations and hit testing.
<svg width="200" height="100" viewBox="0 0 200 100">
<rect id="r" x="10" y="10" width="80" height="50" fill="blue"/>
<script>
var r = document.getElementById("r");
// Get bounding box in SVG user units
var bbox = r.getBBox();
console.log(bbox.x, bbox.y, bbox.width, bbox.height);
// Get bounding box in screen coordinates (after transforms)
var cbox = r.getBoundingClientRect();
console.log(cbox.left, cbox.top, cbox.width, cbox.height);
// Total length of a path or line
var path = document.querySelector("path");
var len = path.getTotalLength();
var point = path.getPointAtLength(len / 2); // midpoint
</script>
</svg>Dataset & Data Attributes
SVG elements support data-* attributes and the dataset property just like HTML. This is the clean way to associate data with shapes for interactive applications — store node IDs, labels, or state without polluting the visual attributes. Read with element.dataset.key, set with the same.
<svg width="200" height="100" viewBox="0 0 200 100">
<circle cx="50" cy="50" r="20" fill="red"
data-id="123" data-label="node"/>
<script>
var circle = document.querySelector("circle");
// dataset reads data-* attributes
console.log(circle.dataset.id); // "123"
console.log(circle.dataset.label); // "node"
// Set data attributes
circle.dataset.selected = "true"; // adds data-selected="true"
// Use for storing state
circle.dataset.visits = "0";
circle.dataset.visits = String(+circle.dataset.visits + 1);
</script>
</svg>Dynamic Path Generation
Generating SVG dynamically is the basis of custom charts. Build the path d attribute by concatenating M/L commands from your data, then create the element with createElementNS. This approach gives full control without a charting library. For complex visualizations, consider D3.js, which wraps this pattern elegantly.
<svg width="300" height="150" viewBox="0 0 300 150" id="chart">
<script>
var NS = "http://www.w3.org/2000/svg";
var svg = document.getElementById("chart");
var data = [10, 50, 30, 80, 45, 90, 25];
// Build a path string from data points
var d = "M 0 " + (150 - data[0]);
for (var i = 1; i < data.length; i++) {
d += " L " + (i * 40) + " " + (150 - data[i]);
}
var path = document.createElementNS(NS, "path");
path.setAttribute("d", d);
path.setAttribute("fill", "none");
path.setAttribute("stroke", "steelblue");
path.setAttribute("stroke-width", "2");
svg.appendChild(path);
// Add dots at each point
data.forEach(function (val, i) {
var c = document.createElementNS(NS, "circle");
c.setAttribute("cx", i * 40);
c.setAttribute("cy", 150 - val);
c.setAttribute("r", 3);
c.setAttribute("fill", "red");
svg.appendChild(c);
});
</script>
</svg>Performance Tips
For performance: batch DOM insertions with DocumentFragment (one reflow), use requestAnimationFrame for JS animations (syncs to display refresh), and avoid querying attributes in tight loops (cache values). SVG excels up to a few hundred elements; for thousands (particles, dense plots), Canvas or WebGL is faster.
<script>
// Batch DOM operations with document fragments
var fragment = document.createDocumentFragment();
for (var i = 0; i < 1000; i++) {
var c = document.createElementNS(NS, "circle");
c.setAttribute("cx", i);
c.setAttribute("cy", 50);
c.setAttribute("r", 2);
fragment.appendChild(c);
}
svg.appendChild(fragment); // one reflow, not 1000
// Use requestAnimationFrame for animations
function animate() {
element.setAttribute("cx", +element.getAttribute("cx") + 1);
requestAnimationFrame(animate);
}
animate();
// For many elements, consider <canvas> instead (faster)
// SVG slows down with thousands of nodes due to DOM overhead
</script>Responsive SVG (ViewBox)
viewBox Basics
viewBox='minX minY width height' defines the internal coordinate system. The first two values pan the view; the last two zoom. The SVG scales this region to fit its width/height. This decouples your drawing coordinates from the rendered size — draw once at any scale, in a 100x100 or 1920x1080 box.
<!-- viewBox="minX minY width height" -->
<svg viewBox="0 0 100 100" width="200" height="200">
<!-- A circle at center of a 100x100 coordinate system -->
<circle cx="50" cy="50" r="50" fill="blue"/>
</svg>
<!-- viewBox shifts the visible area -->
<svg viewBox="50 50 50 50" width="200" height="200">
<!-- Now showing only the bottom-right quadrant -->
<!-- The same circle, but zoomed in 2x on that corner -->
<circle cx="50" cy="50" r="50" fill="blue"/>
</svg>preserveAspectRatio
preserveAspectRatio controls how the viewBox fits the element when aspect ratios differ. 'meet' (default) fits entirely, letterboxing the excess. 'slice' fills entirely, cropping the overflow. 'none' stretches, distorting. The xMidYMid part controls alignment: Min/Mid/Max for both axes.
<!-- Default: preserve aspect ratio, center, letterbox -->
<svg viewBox="0 0 100 50" width="200" height="200"
preserveAspectRatio="xMidYMid meet">
<rect width="100" height="50" fill="blue"/>
</svg>
<!-- "slice": fill entirely, crop overflow -->
<svg viewBox="0 0 100 50" width="200" height="200"
preserveAspectRatio="xMidYMid slice">
<rect width="100" height="50" fill="red"/>
</svg>
<!-- "none": stretch to fill (distorts) -->
<svg viewBox="0 0 100 50" width="200" height="200"
preserveAspectRatio="none">
<rect width="100" height="50" fill="green"/>
</svg>Fully Responsive SVG
For responsive SVG: omit width/height attributes, set viewBox, and control size with CSS (width: 100%, height: auto). The SVG scales to its container while preserving aspect ratio. display: block removes the inline SVG baseline gap. This is the standard pattern for fluid SVG in responsive layouts.
<!-- Omit width/height, set viewBox, use CSS to size -->
<svg viewBox="0 0 100 100" style="width: 100%; height: auto; display: block;">
<circle cx="50" cy="50" r="50" fill="orange"/>
</svg>
<!-- In a container with max-width -->
<div style="max-width: 500px; margin: auto;">
<svg viewBox="0 0 400 300" style="width: 100%; height: auto;">
<rect width="400" height="300" fill="steelblue"/>
</svg>
</div>Nested SVG & overflow
Nested <svg> elements create independent viewports with their own viewBox and preserveAspectRatio. This is powerful for charts with fixed aspect panels or UI components. overflow='visible' lets content draw outside the nested viewport (default is hidden). Each nested svg can scale its content independently.
<svg width="300" height="200" viewBox="0 0 300 200">
<!-- Nested SVG creates a new viewport -->
<svg x="10" y="10" width="100" height="100" viewBox="0 0 50 50">
<circle cx="25" cy="25" r="25" fill="red"/>
</svg>
<!-- overflow="hidden" (default) clips content outside -->
<svg x="120" y="10" width="100" height="100" viewBox="0 0 50 50"
overflow="visible">
<circle cx="25" cy="25" r="30" fill="blue"/>
</svg>
<!-- Each nested svg can have its own preserveAspectRatio -->
<svg x="230" y="10" width="60" height="100" viewBox="0 0 50 50"
preserveAspectRatio="xMidYMid slice">
<circle cx="25" cy="25" r="25" fill="green"/>
</svg>
</svg>Fluid Text Sizing
Font sizes inside SVG with a viewBox scale with the SVG — a 20px font in a 400-wide viewBox displayed at 800px wide renders at 40px. To make text responsive to the browser viewport (not the SVG), use CSS viewport units (vw, vh) in a style attribute. This is how SVG headlines scale fluidly.
<svg viewBox="0 0 400 100" style="width:100%;height:auto;">
<style>
/* Use viewBox units for font-size — scales with the SVG */
text { font-size: 20px; }
/* Note: 'px' here means SVG user units, not CSS px */
</style>
<text x="200" y="50" text-anchor="middle">Scales with SVG</text>
</svg>
<!-- For truly responsive text relative to viewport, use CSS: -->
<svg viewBox="0 0 400 100" style="width:100%;height:auto;">
<text x="200" y="50" text-anchor="middle"
style="font-size: 5vw;">5% of viewport width</text>
</svg>
<!-- Or use container queries / media queries on the SVG element -->Aspect Ratio Control
Combine CSS aspect-ratio on the container with width/height 100% on the SVG for predictable responsive behavior. This prevents layout shift (CLS) by reserving space before the SVG loads. The viewBox matches the aspect ratio so content isn't distorted. This is the modern best practice for responsive SVG embeds.
<!-- Use CSS aspect-ratio for consistent sizing -->
<div style="width: 100%; max-width: 600px; aspect-ratio: 16/9;">
<svg viewBox="0 0 1600 900" style="width:100%;height:100%;">
<rect width="1600" height="900" fill="black"/>
<text x="800" y="450" text-anchor="middle" dominant-baseline="middle"
fill="white" font-size="48">16:9 Container</text>
</svg>
</div>
<!-- aspect-ratio ensures the container keeps proportions -->
<!-- even before the SVG loads, preventing layout shift -->SVG vs Canvas
Rendering Model
SVG is retained mode: every shape is a DOM element you can inspect, style, and re-animate later. Canvas is immediate mode: you issue draw commands that paint pixels, and the shapes don't persist as objects. SVG is better for interactive UI; Canvas is better for thousands of dynamic pixels.
<!-- SVG: retained mode (DOM-based) -->
<svg width="200" height="100">
<circle cx="50" cy="50" r="30" fill="red"/>
<!-- Each element is a DOM node, accessible via JS -->
</svg>
<!-- Canvas: immediate mode (pixel-based) -->
<canvas id="c" width="200" height="100"></canvas>
<script>
var ctx = document.getElementById("c").getContext("2d");
ctx.beginPath();
ctx.arc(50, 50, 30, 0, Math.PI * 2);
ctx.fillStyle = "red";
ctx.fill();
// Pixels are drawn; the circle isn't an object anymore
</script>When to Use SVG
Choose SVG for: resolution-independent graphics (icons, logos), accessibility (screen readers traverse the DOM), interactivity (per-shape events), SEO (text is selectable), and moderate element counts (up to a few hundred). SVG files are text (gzips well) and editable in design tools.
<!-- SVG is ideal for:
- Icons and logos (crisp at any size)
- Charts with few elements (D3.js)
- Interactive diagrams (clickable shapes)
- Illustrations with editable layers
- Accessibility-required graphics
-->
<svg viewBox="0 0 24 24" width="48" height="48">
<path d="M12 2 L2 22 H22 Z" fill="triangle"/>
</svg>
<!-- Stays sharp whether displayed at 16px or 1024px -->
<!-- Each part can have its own click handler -->
<!-- Screen readers can access <title> and <desc> -->When to Use Canvas
Choose Canvas for: high element counts (thousands of shapes), per-frame redraws (games, particles), pixel manipulation (filters, effects), and when you don't need DOM accessibility. Canvas is faster for sheer pixel throughput but harder to make accessible or interactive at the individual shape level.
<!-- Canvas is ideal for:
- Games (hundreds of sprites per frame)
- Particle systems
- Real-time image processing
- Heatmaps with thousands of cells
- Physics simulations
-->
<canvas id="particles" width="800" height="600"></canvas>
<script>
var ctx = document.getElementById("particles").getContext("2d");
var particles = [];
for (var i = 0; i < 5000; i++) {
particles.push({ x: Math.random()*800, y: Math.random()*600 });
}
function draw() {
ctx.clearRect(0, 0, 800, 600);
particles.forEach(function (p) {
ctx.fillRect(p.x, p.y, 2, 2);
});
requestAnimationFrame(draw);
}
draw();
</script>Performance Comparison
SVG performance degrades with element count because each shape is a DOM node with event handling, styling, and layout. Canvas has constant overhead per frame regardless of shape count. The crossover is around 500-2000 elements depending on complexity. For static or lightly-animated graphics, SVG is fine; for dense real-time scenes, use Canvas.
<!-- SVG DOM overhead grows with element count -->
<!-- 100 circles: SVG ~0.1ms, Canvas ~0.1ms (both fine) -->
<!-- 1000 circles: SVG ~10ms, Canvas ~1ms (Canvas pulls ahead) -->
<!-- 10000 circles: SVG ~500ms+, Canvas ~10ms (SVG struggles) -->
<!-- SVG advantages:
- No redraw needed for CSS changes (browser handles it)
- Hardware-accelerated transforms
- Only changed elements re-render (incremental)
-->
<!-- Canvas advantages:
- Constant redraw cost regardless of history
- No DOM memory overhead per shape
- Direct pixel access (getImageData, putImageData)
-->Hybrid Approach
Combine both: Canvas for the dense, frequently-redrawn layer (heatmap, particles) and SVG overlaid for interactive UI (tooltips, selection handles, labels). The SVG layer stays crisp and accessible while the Canvas handles the heavy pixel work. This pattern is used by mapping libraries like Leaflet and charting tools like ECharts.
<!-- SVG for UI chrome, Canvas for dense content -->
<div style="position:relative; width:800px; height:600px;">
<!-- Canvas renders the heatmap (fast for thousands of cells) -->
<canvas width="800" height="600" style="position:absolute; top:0; left:0;">
</canvas>
<!-- SVG overlay for interactive tooltips and selection -->
<svg width="800" height="600" viewBox="0 0 800 600"
style="position:absolute; top:0; left:0;">
<g id="overlay"></g>
</svg>
</div>
<script>
// Draw heatmap on canvas (performant)
// Add hover tooltips as SVG elements (accessible, styled)
</script>Accessibility Comparison
SVG is inherently more accessible: the DOM is traversable, text is selectable and searchable, and <title>/<desc> provide descriptions. Canvas requires extra work — you must add role/aria-label and provide text fallback content inside the <canvas> tag. For charts and data visualizations, SVG's accessibility is a major advantage.
<!-- SVG: screen readers traverse the DOM -->
<svg role="img" aria-label="Sales chart showing 20% growth">
<title>Sales Chart</title>
<desc>Bar chart with 5 quarters, Q3 highest at $2M</desc>
<rect .../> <!-- each bar is a DOM element -->
</svg>
<!-- Text in SVG is selectable and searchable -->
<!-- Canvas: just pixels to a screen reader -->
<canvas role="img" aria-label="Sales chart showing 20% growth">
<!-- Must provide text fallback inside the canvas tag -->
Sales chart: Q1 $1M, Q2 $1.3M, Q3 $2M, Q4 $1.8M
</canvas>
<!-- No DOM, no selectable text, harder to make accessible -->SVG Sprite
Inline Sprite Setup
Define all icons as <symbol> elements inside a hidden SVG at the top of the page. Each symbol has its own viewBox and id. The sprite loads once and is cached for the session. Using currentColor for fill/stroke lets each usage inherit the surrounding text color, making theming trivial.
<!-- Place this hidden SVG at the top of your HTML body -->
<svg xmlns="http://www.w3.org/2000/svg" style="display:none" aria-hidden="true">
<symbol id="icon-home" viewBox="0 0 24 24">
<path d="M12 3 L2 12 H5 V21 H10 V14 H14 V21 H19 V12 H22 Z"/>
</symbol>
<symbol id="icon-user" viewBox="0 0 24 24">
<circle cx="12" cy="8" r="4"/>
<path d="M4 21 C 4 16, 8 14, 12 14 C 16 14, 20 16, 20 21 Z"/>
</symbol>
<symbol id="icon-search" viewBox="0 0 24 24">
<circle cx="11" cy="11" r="7" fill="none" stroke="currentColor" stroke-width="2"/>
<line x1="16" y1="16" x2="21" y2="21" stroke="currentColor" stroke-width="2"/>
</symbol>
</svg>Using Sprite Icons
Each <use> instantiates an icon. The CSS .icon class sets a default size; subclasses override for variants. Because the symbols use fill='currentColor', the icon's color follows the CSS color property — set color on a button and its icon matches. This is how major icon systems (GitHub, Bootstrap) work.
<!-- Reference any icon with <use> -->
<svg class="icon"><use href="#icon-home"/></svg>
<svg class="icon"><use href="#icon-user"/></svg>
<svg class="icon icon-large"><use href="#icon-search"/></svg>
<style>
.icon {
width: 24px;
height: 24px;
fill: currentColor; /* inherits text color */
display: inline-block;
vertical-align: middle;
}
.icon-large { width: 48px; height: 48px; }
</style>
<!-- Color via CSS color (because fill="currentColor") -->
<button style="color: red;">
<svg class="icon"><use href="#icon-home"/></svg> Home
</button>Sprite from External File
External sprite references (href='file.svg#id') have spotty browser support and CORS issues. The robust approach: fetch the sprite file and inject it as inline HTML at the top of the body. This caches via HTTP and works everywhere. Many build tools (svg-sprite-loader, gulp-svg-sprite) automate this.
<!-- icons.svg (a separate file) -->
<svg xmlns="http://www.w3.org/2000/svg" style="display:none">
<symbol id="icon-home" viewBox="0 0 24 24">...</symbol>
</svg>
<!-- Reference external symbols (limited browser support) -->
<svg class="icon"><use href="icons.svg#icon-home"/></svg>
<!-- More reliable: fetch and inject the sprite -->
<script>
fetch("icons.svg")
.then(r => r.text())
.then(text => {
var div = document.createElement("div");
div.style.display = "none";
div.innerHTML = text;
document.body.insertBefore(div, document.body.firstChild);
});
</script>Build-Time Sprite Generation
Build tools automate sprite creation: drop individual .svg files in a folder, and the tool concatenates them into a sprite with proper <symbol> wrappers. svg-sprite-loader (webpack), gulp-svg-sprite, and vite-plugin-svg are popular. This keeps icons as separate files during development but bundles them for production.
<!-- Using svg-sprite-loader (webpack) -->
// import all icons in a folder
const req = require.context("./icons", true, /\.svg$/);
req.keys().forEach(req);
// In your component:
<svg class="icon"><use href="#icon-home"/></svg>
<!-- Using gulp-svg-sprite -->
// gulpfile.js
const svgSprite = require("gulp-svg-sprite");
gulp.task("sprite", () =>
gulp.src("icons/*.svg")
.pipe(svgSprite({ mode: { symbol: { dest: "." } } }))
.pipe(gulp.dest("dist"))
);
// Outputs dist/symbol/svg/sprite.symbol.svgAccessibility for Sprite Icons
For accessibility: if an icon is purely decorative (next to text), mark it aria-hidden='true'. If it conveys meaning alone, add role='img' and aria-label on the <svg>. For icon-only buttons, put the aria-label on the <button>, not the SVG. Never rely on the icon alone without an accessible name.
<!-- Decorative icon (hidden from screen readers) -->
<svg class="icon" aria-hidden="true">
<use href="#icon-decoration"/>
</svg>
<!-- Meaningful icon (with label) -->
<svg class="icon" role="img" aria-label="Home">
<use href="#icon-home"/>
</svg>
<!-- Icon button with visible text (icon is decorative) -->
<button>
<svg class="icon" aria-hidden="true"><use href="#icon-home"/></svg>
<span>Home</span>
</button>
<!-- Icon-only button (needs aria-label on the button) -->
<button aria-label="Search">
<svg class="icon" aria-hidden="true"><use href="#icon-search"/></svg>
</button>Two-Tone & Multi-Color Icons
Multi-color icons are tricky with sprites because <use> clones are limited in what you can override. For two-tone icons, use currentColor for the primary and a fixed color for the secondary. CSS custom properties (variables) inside the symbol let consumers override specific parts — a modern technique for flexible theming.
<svg style="display:none">
<!-- Icon with two fill regions -->
<symbol id="icon-mail" viewBox="0 0 24 24">
<rect x="2" y="4" width="20" height="16" rx="2" fill="currentColor"/>
<path d="M2 6 L12 13 L22 6" fill="white"/>
</symbol>
</svg>
<!-- Override individual parts via CSS (limited: works if the
symbol uses currentColor and CSS variables) -->
<svg class="icon" style="color: steelblue;">
<use href="#icon-mail"/>
</svg>
<!-- For full multi-color control, use CSS custom properties -->
<symbol id="icon-alert" viewBox="0 0 24 24">
<path fill="var(--icon-bg, currentColor)" d="M12 2 L2 22 H22 Z"/>
<text x="12" y="18" text-anchor="middle" fill="var(--icon-fg, white)">!</text>
</symbol>
<svg class="icon" style="--icon-bg: red; --icon-fg: yellow;">
<use href="#icon-alert"/>
</svg>SVG Optimization
SVGO CLI
SVGO is the standard SVG optimizer. It removes metadata, comments, editor cruft (Inkscape/Illustrator namespaces), unused defs, and redundant attributes, often cutting file size 30-60%. Run it on all production SVGs. Use --show-plugins to see what it can do, and a config file to enable/disable specific optimizations.
# Install SVGO globally
npm install -g svgo
# Optimize a single file (overwrites)
svgo icon.svg
# Optimize to a new file
svgo icon.svg -o icon.min.svg
# Optimize a folder
svgo -f icons/ -o icons/min/
# Show available plugins
svgo --show-plugins
# Use a specific config
svgo --config=svgo.config.js icon.svgCommon Optimizations
SVGO removes: XML declarations, editor comments and namespaces (Adobe, Inkscape), metadata, unused xlink, enable-background, xml:space, redundant fill-rule='nonzero' (it's the default). It shortens color values (#FF0000 → red), collapses whitespace, and merges paths. The result is semantically identical but much smaller.
<!-- Before optimization (from Illustrator) -->
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.0 -->
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
width="100px" height="100px" viewBox="0 0 100 100"
enable-background="new 0 0 100 100" xml:space="preserve">
<metadata>...</metadata>
<path d="..." fill="#FF0000" fill-rule="nonzero"/>
</svg>
<!-- After SVGO -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<path d="..." fill="red"/>
</svg>Path Optimization
SVGO converts absolute coordinates to relative where shorter, uses H/V instead of L for horizontal/vertical lines, removes unnecessary decimals, and can merge paths with identical shapes. These path optimizations often halve path string length. The 'convertPath' plugin handles this; it's on by default.
<!-- SVGO can merge and simplify paths -->
<!-- Before: multiple paths with redundant points -->
<g>
<path d="M 10 10 L 20 10 L 20 20 L 10 20 Z" fill="red"/>
<path d="M 10 10 L 20 10 L 20 20 L 10 20 Z" fill="none" stroke="black"/>
</g>
<!-- After: merged into one path with combined commands -->
<path d="M10 10h10v10H10z" fill="red" stroke="black"/>
<!-- Path command shortcuts:
L 20 10 -> h 10 (relative horizontal)
L 10 20 -> v 10 (relative vertical)
Z closes the path
-->Grouping & Inheritance
Move shared attributes (fill, stroke, font) to a parent <g> so children inherit them. This reduces file size and makes bulk style changes easier. SVGO's 'mergePaths' and 'moveElemsAttrsToGroup' plugins automate this. When hand-writing SVG, group from the start — it's cleaner and more maintainable.
<!-- Before: repeated attributes on each element -->
<svg viewBox="0 0 100 100">
<rect x="10" y="10" width="20" height="20" fill="red" stroke="black" stroke-width="2"/>
<rect x="40" y="10" width="20" height="20" fill="red" stroke="black" stroke-width="2"/>
<rect x="70" y="10" width="20" height="20" fill="red" stroke="black" stroke-width="2"/>
</svg>
<!-- After: group with shared attributes -->
<svg viewBox="0 0 100 100">
<g fill="red" stroke="black" stroke-width="2">
<rect x="10" y="10" width="20" height="20"/>
<rect x="40" y="10" width="20" height="20"/>
<rect x="70" y="10" width="20" height="20"/>
</g>
</svg>Gzip & Delivery
SVG gzips 70-90% because it's repetitive XML text. Ensure your web server compresses image/svg+xml with gzip or brotli. For delivery: inline critical (above-the-fold) SVG to avoid a render-blocking request; use <img loading='lazy'> for below-the-fold illustrations. Cache SVG aggressively — it rarely changes.
<!-- SVG is text, so it gzips very well (70-90% reduction) -->
<!-- Ensure your server sends SVG with gzip/brotli compression: -->
<!-- Apache .htaccess -->
AddType image/svg+xml .svg
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE image/svg+xml
</IfModule>
<!-- Nginx -->
# gzip_types image/svg+xml;
<!-- HTML: use the right loading strategy -->
<img src="hero.svg" loading="lazy" decoding="async">
<!-- For above-the-fold critical SVG, inline it to avoid a request -->
<!-- For below-the-fold, use <img loading="lazy"> to defer -->SVGO Config File
A config file customizes SVGO. Common tweaks: keep viewBox (removeViewBox is on by default — turn it OFF so responsiveness works), remove width/height for fluid SVGs (removeDimensions), and enable cleanupIDs for sprites (shortens id names). multipass runs optimizations until stable for the smallest output.
// svgo.config.js (SVGO 2+)
module.exports = {
multipass: true, // run multiple passes for best result
js2svg: {
indent: 2,
pretty: true, // readable output (false for minified)
},
plugins: [
{ name: "preset-default" }, // sensible defaults
{ name: "removeDimensions", active: true }, // remove width/height (use viewBox)
{ name: "removeViewBox", active: false }, // KEEP viewBox (opposite of default)
{ name: "removeXMLNS", active: false }, // keep xmlns for standalone files
{ name: "cleanupIDs", active: true }, // minify id names
{ name: "mergePaths", active: true },
],
};Best Practices
Accessibility Essentials
For accessible SVG: add <title> and <desc>, reference them via aria-labelledby, set role='img' so screen readers announce the SVG as an image, and add focusable='false' to prevent IE/Edge tab-stop issues. Decorative SVGs should be aria-hidden='true'. These practices make SVG usable with assistive technology.
<!-- 1. Always include <title> and <desc> for meaningful SVGs -->
<svg role="img" aria-labelledby="title-id desc-id">
<title id="title-id">Annual Revenue Chart</title>
<desc id="desc-id">A bar chart showing revenue from 2019 to 2023,
rising from $1M to $5M.</desc>
<!-- chart content -->
</svg>
<!-- 2. Decorative SVG: hide from screen readers -->
<svg aria-hidden="true" focusable="false">
<!-- purely visual flourish -->
</svg>
<!-- 3. focusable="false" prevents IE/Edge from making SVG focusable -->
<!-- 4. Use role="img" so screen readers treat SVG as a single image -->Use viewBox, Not width/height
Always include viewBox — it defines the internal coordinate system and enables scaling. For icons, set width/height via CSS (not attributes) so the same SVG works at any size. For responsive graphics, omit width/height entirely and use width:100%; height:auto in CSS. SVGO's removeDimensions plugin enforces this.
<!-- GOOD: viewBox makes the SVG scalable -->
<svg viewBox="0 0 24 24" style="width: 24px; height: 24px;">
<path d="..."/>
</svg>
<!-- BAD: fixed width/height limits scalability -->
<svg width="24" height="24">
<path d="..."/>
</svg>
<!-- For responsive: omit width/height, size with CSS -->
<svg viewBox="0 0 100 100" style="width:100%;height:auto;">
<circle cx="50" cy="50" r="50"/>
</svg>currentColor for Theming
Use fill='currentColor' (or stroke) so icons inherit the surrounding text color. This makes theming trivial — set color on a parent and all icons match. It also enables hover/active states via CSS color transitions. Avoid hardcoding colors in reusable symbols unless they're intentionally fixed (like a brand logo).
<!-- GOOD: use currentColor so the icon inherits text color -->
<symbol id="icon" viewBox="0 0 24 24">
<path fill="currentColor" d="..."/>
</symbol>
<!-- Usage: color via CSS -->
<button style="color: tomato;">
<svg class="icon"><use href="#icon"/></svg> Themed
</button>
<button style="color: steelblue;">
<svg class="icon"><use href="#icon"/></svg> Also themed
</button>
<!-- BAD: hardcoded colors can't be themed -->
<symbol id="icon">
<path fill="#333333" d="..."/> <!-- always gray -->
</symbol>Semantic Grouping
Group elements by their logical role (background, axes, data, labels) with meaningful ids, not just by shape type. This makes the SVG easier to maintain, style, and script — you can show/hide or restyle entire groups. It also helps with accessibility (aria-labelledby on groups) and debugging.
<svg viewBox="0 0 200 200">
<!-- Group by logical component, not just by shape type -->
<g id="background">
<rect width="200" height="200" fill="#f0f0f0"/>
</g>
<g id="chart-axis">
<line x1="20" y1="180" x2="180" y2="180" stroke="black"/>
<line x1="20" y1="20" x2="20" y2="180" stroke="black"/>
</g>
<g id="chart-data" fill="steelblue">
<rect x="30" y="100" width="20" height="80"/>
<rect x="60" y="60" width="20" height="120"/>
<rect x="90" y="80" width="20" height="100"/>
</g>
<g id="labels" font-size="10" fill="black">
<text x="40" y="195" text-anchor="middle">Q1</text>
<text x="70" y="195" text-anchor="middle">Q2</text>
</g>
</svg>Avoid Overdraw & Complexity
Overdraw (drawing shapes that are fully covered) wastes render time — remove hidden elements. Simplify paths: use C/Q curves instead of hundreds of tiny line segments; reduce decimal precision (10.499999 → 10.5). For complex illustrations, consider whether a raster image would be smaller and faster. Profile with the browser's paint profiler.
<!-- BAD: layered shapes that cover each other (overdraw) -->
<g>
<rect width="100" height="100" fill="red"/>
<rect width="100" height="100" fill="blue"/> <!-- covers red -->
<rect width="100" height="100" fill="green"/> <!-- covers blue -->
</g>
<!-- The red and blue are never visible but still rendered -->
<!-- GOOD: only draw what's visible -->
<rect width="100" height="100" fill="green"/>
<!-- BAD: path with 10,000 points for a simple shape -->
<path d="..."/> <!-- over-tessellated -->
<!-- GOOD: simplify paths; use curves instead of many line segments -->
<path d="M 10 50 Q 50 10, 90 50"/> <!-- one curve vs 100 lines -->Testing & Validation
Test SVGs like any code: validate the XML, check cross-browser rendering (Safari has quirks with some filters), test at various sizes (16px icons vs 500px heroes), verify screen reader output, and check file size (icons should be under 2KB). Run SVGO in CI to prevent unoptimized SVGs from shipping. A little testing prevents production surprises.
<!-- 1. Validate SVG XML -->
<!-- Use the W3C validator: https://validator.w3.org/ -->
<!-- 2. Test in multiple browsers -->
<!-- Chrome, Firefox, Safari, Edge, (IE if required) -->
<!-- 3. Test at different sizes -->
<div style="width: 16px;"><svg>...</svg></div> <!-- icon -->
<div style="width: 500px;"><svg>...</svg></div> <!-- hero -->
<!-- 4. Test with screen readers (VoiceOver, NVDA) -->
<!-- 5. Check file size after optimization -->
ls -la icon.svg # should be under 2KB for simple icons
<!-- 6. Verify currentColor theming works -->
<!-- 7. Ensure no hardcoded dimensions block responsiveness -->Fragmentos de SVG relacionados
Copy-paste ready code for common tasks.
Basic Shapes
Rect, circle, ellipse, line, polygon, polyline.
Paths
Draw arbitrary curves via the d attribute.
Gradients
Linear and radial color blends.
Transforms
Translate, rotate, scale, and skew groups.
Text
Styled text and tspans.
Filters
Blur, shadow, and other effects.
Animation
SMIL animate, transform, and opacity.
Patterns
Tileable fills defined in defs.
Was this helpful?