Reuse: defs, use, symbol & markers

Define graphics once and reuse them — the foundation of SVG icon systems

<defs> + <use>

Content placed inside <defs> is parsed and stored by the browser but never painted directly. Think of it as a private template library sitting at the top of your SVG. To actually draw a piece of that library you stamp it onto the canvas with <use href="#id">.

This separation of definition and instantiation is the key to keeping SVG files small and consistent. Change the shape in <defs> once and every <use> instance updates automatically — the same principle behind CSS classes.

Defining a shape and instancing it

The example below defines a single hexagon inside <defs>, then stamps it out three times. Each <use> element can supply its own x/y offset and override fill — but the geometry comes entirely from the one definition.

<svg viewBox="0 0 260 100" width="320" height="123" role="img" aria-label="Three hexagons in different colors, all sharing one definition"> <defs> <!-- Defined once, never rendered directly --> <polygon id="hex" points="30,0 60,17 60,52 30,70 0,52 0,17" fill="#1f8a4c" /> </defs> <!-- Instance 1: default green fill from the definition --> <use href="#hex" x="10" y="15" /> <!-- Instance 2: red fill overrides definition --> <use href="#hex" x="100" y="15" fill="#d6452c" /> <!-- Instance 3: orange fill override --> <use href="#hex" x="190" y="15" fill="#e07b00" /> </svg>

The fill attribute on a <use> element feeds into the shadow DOM that the browser creates for each instance. Because fill is an inherited presentation attribute, a value set on the <use> wrapper is inherited by the content inside — overriding the definition's own fill. Properties that are not inherited (like stroke-width) would need a more targeted approach, such as CSS custom properties.

Resizing instances with width and height

When the referenced element is a <symbol> (covered next), you can resize instances by setting width and height on <use>. For plain shapes such as the hexagon above, the most reliable way to scale is with a transform="scale(…)" on the <use> element, because plain shapes have no viewBox to scale into.

<svg viewBox="0 0 260 120" width="320" height="148" role="img" aria-label="Three hexagons at different scales"> <defs> <polygon id="hex2" points="30,0 60,17 60,52 30,70 0,52 0,17" fill="#0f766e" /> </defs> <!-- Small instance --> <use href="#hex2" transform="translate(10,40) scale(0.6)" /> <!-- Medium instance --> <use href="#hex2" transform="translate(70,20) scale(0.85)" /> <!-- Full-size instance --> <use href="#hex2" transform="translate(155,5)" /> </svg>

<symbol> + <use> — icon systems

<symbol> is like <defs> but purpose-built for icons. A symbol carries its own viewBox, which means each instance can be given a width and height and the symbol's content will scale to fit — exactly the same way that a standalone SVG file scales. This is the native SVG equivalent of an image sprite.

In production, all your symbols live in a single hidden <svg> that you include once per page (or load as a separate .svg file and reference with <use href="sprite.svg#icon-id">). Every icon on the page then becomes a single lightweight element with no duplicated path data.

Three icons defined as symbols

The hidden <svg> below contains three symbols. They are invisible until referenced. Each <use> gives the icon a size via width/height and optionally overrides the color.

<!-- Hidden sprite container (typically at top of <body>) --> <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" style="position:absolute;width:0;height:0;overflow:hidden"> <defs> <symbol id="icon-check" viewBox="0 0 24 24"> <!-- Checkmark --> <path d="M4 13 L9 18 L20 7" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" /> </symbol> <symbol id="icon-warning" viewBox="0 0 24 24"> <!-- Triangle with exclamation --> <path d="M12 3 L22 20 L2 20 Z" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round" /> <line x1="12" y1="10" x2="12" y2="15" stroke="currentColor" stroke-width="2" stroke-linecap="round" /> <circle cx="12" cy="18" r="1" fill="currentColor" /> </symbol> <symbol id="icon-close" viewBox="0 0 24 24"> <!-- X mark --> <line x1="5" y1="5" x2="19" y2="19" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" /> <line x1="19" y1="5" x2="5" y2="19" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" /> </symbol> </defs> </svg> <!-- Use icons at different sizes and colors --> <svg viewBox="0 0 300 100" width="360" height="120" role="img" aria-label="Three icons at different sizes"> <use href="#icon-check" x="10" y="10" width="24" height="24" color="#1f8a4c" /> <use href="#icon-warning" x="50" y="5" width="36" height="36" color="#e07b00" /> <use href="#icon-close" x="105" y="0" width="48" height="48" color="#d6452c" /> <!-- Same icons, larger --> <use href="#icon-check" x="165" y="5" width="40" height="40" color="#0f766e" /> <use href="#icon-warning" x="215" y="0" width="60" height="60" color="#d6452c" /> </svg>

Notice that the icon paths use stroke="currentColor" and fill="currentColor" rather than hard-coded hex values. The color CSS property set on the <use> element flows down as currentColor, so you can theme an entire icon set just by changing one property — or by setting color in CSS with a class. This is the standard technique used by icon libraries like Heroicons and Phosphor.

How an external sprite sheet works

In a real project you would put all your <symbol> definitions in a single file, say icons.svg, and reference them across pages without any inline markup:

<!-- Reference a symbol from an external SVG file --> <svg width="32" height="32" aria-label="Check icon" role="img"> <use href="/assets/icons.svg#icon-check" /> </svg>

The browser downloads icons.svg once and caches it. All subsequent icon uses are free. Cross-origin references require the server to send a permissive Access-Control-Allow-Origin header, but on the same origin they always work.

<marker> — arrowheads and path decorations

A <marker> is a small graphic that is automatically painted at specific points along a path or line. The three attachment properties are:

  • marker-start — placed at the very first point of the path.
  • marker-mid — placed at every intermediate point (between start and end).
  • marker-end — placed at the very last point, typically used for arrowheads.

The key marker attributes:

  • markerWidth / markerHeight — the size of the marker's bounding box in marker units.
  • refX / refY — the point within the marker that lines up with the path vertex. For an arrowhead you usually set refX to the tip of the arrow.
  • orient="auto" — rotates the marker so it always points along the path direction. Use orient="auto-start-reverse" on marker-start to flip it automatically.
  • markerUnits="strokeWidth" (default) — marker dimensions are multiples of the stroke width, so the arrowhead scales with the line thickness.

Simple arrowhead on a line

Define the arrowhead shape inside <defs><marker>, then apply it to a <line> or <path> via the marker-end attribute.

<svg viewBox="0 0 200 80" width="280" height="112" role="img" aria-label="A line with a red arrowhead at its end"> <defs> <marker id="arrow-red" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto"> <!-- Arrowhead shape drawn in marker coordinate space --> <path d="M0 0 L8 4 L0 8 Z" fill="#d6452c" /> </marker> </defs> <line x1="20" y1="40" x2="170" y2="40" stroke="#d6452c" stroke-width="2" marker-end="url(#arrow-red)" /> </svg>

The refX="6" means "align the point 6 units along the marker's x-axis with the path endpoint." Since the arrowhead tip is drawn at x=8, setting refX to 6 leaves a small gap — the visible tip of the arrow aligns cleanly with the line end. Adjust refX if you notice the arrow overshooting or undershooting.

Both ends, and a curved path

You can apply different markers to start and end. Here a double-headed arrow labels a curved path segment, demonstrating marker-start with orient="auto-start-reverse" so both heads point outward.

<svg viewBox="0 0 260 120" width="320" height="148" role="img" aria-label="A curved double-headed arrow in teal, with a second straight orange arrow below"> <defs> <marker id="arrow-teal-end" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto"> <path d="M0 0 L8 4 L0 8 Z" fill="#0f766e" /> </marker> <marker id="arrow-teal-start" markerWidth="8" markerHeight="8" refX="2" refY="4" orient="auto-start-reverse"> <path d="M0 0 L8 4 L0 8 Z" fill="#0f766e" /> </marker> <marker id="arrow-orange" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto"> <path d="M0 0 L8 4 L0 8 Z" fill="#e07b00" /> </marker> </defs> <!-- Double-headed curved arrow --> <path d="M30 60 Q130 10 230 60" fill="none" stroke="#0f766e" stroke-width="2" marker-start="url(#arrow-teal-start)" marker-end="url(#arrow-teal-end)" /> <!-- Straight arrow below --> <line x1="30" y1="95" x2="230" y2="95" stroke="#e07b00" stroke-width="2" marker-end="url(#arrow-orange)" /> </svg>

The orient="auto-start-reverse" value on the start marker tells the browser to rotate the marker as normal for a marker-end but then flip it 180°. This means you can define the arrowhead shape pointing right once and reuse it on both ends without a separate "left-pointing" definition.

Dot markers at midpoints

Markers are not limited to arrowheads. Using marker-mid, you can place a decoration at every vertex of a polyline — useful for labeling data points on a chart or diagram.

<svg viewBox="0 0 240 100" width="300" height="125" role="img" aria-label="A polyline with green dot markers at each vertex"> <defs> <marker id="dot-green" markerWidth="6" markerHeight="6" refX="3" refY="3" orient="auto"> <circle cx="3" cy="3" r="3" fill="#1f8a4c" /> </marker> </defs> <polyline points="20,80 60,30 100,55 140,20 180,45 220,15" fill="none" stroke="#333" stroke-width="1.5" marker-start="url(#dot-green)" marker-mid="url(#dot-green)" marker-end="url(#dot-green)" /> </svg>