SVG arrow: add an arrowhead to a line with marker

Draw an arrow in SVG by putting a marker on the end of a line or path. How refX, orient and markerUnits work, arrowheads at both ends, colour and faults.

Published

SVG has no arrow element. An arrow is a line with a <marker> on its end: a small drawing, defined once, that the browser places at the end of the line and turns to point along it.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 40">
  <defs>
    <marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5"
      markerWidth="6" markerHeight="6" orient="auto">
      <path d="M 0 0 L 10 5 L 0 10 Z"/>
    </marker>
  </defs>
  <line x1="10" y1="20" x2="80" y2="20" stroke="black" stroke-width="2"
    marker-end="url(#arrow)"/>
</svg>

A black line with a solid triangular head pointing right. Change the line’s end point and the head follows and turns with it.

If you only want an arrow shape to cut or to use as a picture, skip the code: the arrow SVG files are free to download.

The parts of a marker

AttributeMeaningDefault
idThe name the line refers toNone
viewBoxThe coordinate system the marker’s own shapes are drawn inNone
markerWidth, markerHeightThe size of the box the viewBox is fitted into3
markerUnitsWhat that size is measured in: strokeWidth or userSpaceOnUsestrokeWidth
refX, refYThe point of the marker’s drawing that is pinned to the end of the line0
orientThe rotation: auto, auto-start-reverse or a fixed angle0

A marker draws nothing where it is defined. It appears only on a shape that names it in one of three properties:

PropertyWhere the marker goes
marker-startThe first point
marker-endThe last point
marker-midEvery point in between

In the example, the triangle is drawn in a 10 by 10 box with its tip at 10,5. markerWidth="6" with the default markerUnits makes that box six stroke widths across. The stroke is 2 wide, so the head is 12 units long and 12 high.

refX and refY: which point is pinned

refX and refY name a point in the marker’s own coordinates. That point is placed exactly on the end of the line.

refX in this markerWhat sits on the line’s endResult
10The tipThe arrow ends where the line ends, but the square end of the line shows on either side of the point, so the tip looks blunt
8A point just behind the tipThe end of the line is hidden inside the head. The tip lands 2.4 units past the end
0The flat back of the headThe whole head lies beyond the line, 12 units past the end

All three were rendered in Chrome and the edge pixels read back. The blunt tip in the first row is the usual complaint about marker arrows, and moving refX back a little is the cure. With refX="8" or refX="0" the arrow reaches further than the line’s end point, so stop the line that much short of its target.

refY="5" is the middle of the 10 unit box, which centres the head on the line.

orient: pointing along the line

orient="auto" turns the marker so that its x axis runs along the line at that point. Draw the head pointing right and it will point the way the line is going. It works on curves too:

<path d="M 10 10 Q 60 10 60 30" fill="none" stroke="black" stroke-width="2"
  marker-end="url(#arrow)"/>

The path bends from heading right to heading down, and the head points down. Without orient, the default angle of 0 leaves every head pointing right whatever the line does.

An arrowhead at both ends

A marker at the start of a line with orient="auto" points along the line too, which means into it: the wrong way for a double-headed arrow. orient="auto-start-reverse" turns the marker round when it is used as marker-start, so one definition serves both ends:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 40">
  <defs>
    <marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5"
      markerWidth="6" markerHeight="6" orient="auto-start-reverse">
      <path d="M 0 0 L 10 5 L 0 10 Z"/>
    </marker>
  </defs>
  <line x1="20" y1="20" x2="80" y2="20" stroke="black" stroke-width="2"
    marker-start="url(#arrow)" marker-end="url(#arrow)"/>
</svg>

A line with a head at each end, both pointing outwards.

Size: markerUnits

By default a marker scales with the stroke. Double the stroke-width and the head doubles too, which keeps the proportions of the arrow.

To give the head a fixed size whatever the stroke, add markerUnits="userSpaceOnUse". markerWidth="6" then means 6 units of the drawing. With that setting, heads on a line 1 wide and a line 3 wide came out the same size in the test.

Colour

A marker does not take the colour of the line it sits on. The SVG 2 specification says properties do not inherit from the referencing element into the marker, and it shows: put the example’s marker on a red line and the head stays black.

Three ways to deal with it.

Set the colour in the marker. Write <path d="..." fill="crimson"/> inside it. This is simple and works everywhere. Lines of different colours need a marker each, with different ids.

Use context-stroke. fill="context-stroke" on the marker’s path means “the stroke of whatever line is using me”. One marker then matches every line:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 40">
  <defs>
    <marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5"
      markerWidth="6" markerHeight="6" orient="auto">
      <path d="M 0 0 L 10 5 L 0 10 Z" fill="context-stroke"/>
    </marker>
  </defs>
  <line x1="10" y1="10" x2="80" y2="10" stroke="crimson" stroke-width="2" marker-end="url(#arrow)"/>
  <line x1="10" y1="30" x2="80" y2="30" stroke="teal" stroke-width="2" marker-end="url(#arrow)"/>
</svg>

A red arrow and a teal arrow from one marker. This worked in Chrome. MDN’s compatibility data lists context-stroke from Chrome 124 and Firefox 111 and marks it as unsupported in Safari, so do not rely on it alone.

Use currentColor. Give both the line and the marker’s path currentColor and set color on an element that contains them both, such as the <svg>. Setting color on the line alone does not reach the marker.

marker-mid

marker-mid puts a marker on every point between the first and the last. It suits dots on the corners of a chart line:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 40">
  <defs>
    <marker id="dot" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="5" markerHeight="5">
      <circle cx="5" cy="5" r="5" fill="teal"/>
    </marker>
  </defs>
  <polyline points="10,30 35,10 60,25 90,10" fill="none" stroke="black"
    marker-start="url(#dot)" marker-mid="url(#dot)" marker-end="url(#dot)"/>
</svg>

A zigzag with a teal dot on each of its four points.

“Every point in between” means the points written in the data, not places along the line. A <line> has only a start and an end, so marker-mid on a line draws nothing. A path made of one curve has no middle point either. To put an arrowhead half way along, split the line in two at that spot: <polyline points="10,20 45,20 80,20"/> has a middle point to carry it.

Which shapes take markers

In Chrome, markers were drawn on <line>, <polyline>, <polygon> and <path>. On a <rect> they were not. The marker properties can be written as attributes, as above, or in CSS: .flow { marker-end: url(#arrow) }. The shorthand marker: url(#dot) sets all three at once.

An arrow without a marker

For a single arrow, a marker may be more machinery than the job needs. A path can draw the shaft and an open head in one go:

<path d="M 10 20 H 80 M 70 10 L 80 20 L 70 30" fill="none" stroke="black"
  stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>

And a block arrow is a seven-point polygon:

<polygon points="10,15 60,15 60,5 85,20 60,35 60,25 10,25" fill="teal"/>

Both are plain shapes, so they can be rotated with a transform and are read by any software. That matters for cutting and plotting: support for markers outside browsers is uneven, and an arrow that has to survive an import is safer drawn as a path or polygon.

Common problems

What you seeCauseFix
No arrowheadThe url(#id) does not match the marker’s id, or the marker has no sizeCheck the id. Keep markerWidth and markerHeight above 0
Half the head is cut offMarker contents are clipped to the marker’s box, and the shapes run outside itAdd a viewBox that holds the whole head, or overflow="visible" on the marker
The tip looks bluntThe line’s square end shows beside the tipMove refX back from the tip
The head points the wrong wayNo orient, or auto used at the start of the lineorient="auto", or auto-start-reverse for both ends
The head is the wrong colourMarkers do not inherit from the lineSet fill in the marker, or context-stroke
The head is huge on a thick linemarkerUnits is strokeWidth by defaultmarkerUnits="userSpaceOnUse"
marker-mid shows nothingThe shape has no points between its endsAdd a point where the marker should go
A click on the head is not noticedMarkers do not receive events, and in Chrome a click on the head does not count as a click on the line eitherDraw the head as a real shape and listen on that
The arrow is cut off at the edge of the drawinggetBBox() and a viewBox fitted to the line leave markers outAdd room for the head

Questions

How do I draw a curved arrow? Put the marker on a <path> with a curve in it. orient="auto" lines the head up with the end of the curve. The SVG path guide covers the curve commands.

How do I draw a dashed arrow? Add stroke-dasharray to the line. The marker is a separate drawing and stays solid. See dashed lines.

Can the marker be any shape? Yes. Anything that can go in an SVG can go in a marker: an open V, a circle, a bar, a diamond. Draw it pointing right, in a small viewBox, and set refX and refY to the point that should sit on the line.

Why keep markers in <defs>? A marker is never drawn where it is written, so <defs> is only tidiness. It makes clear to a reader that these are definitions. SVG g and use explains <defs>.