SVG code editor with live drawing canvas, arrow and text annotation, and multi-format export

File
Edit
View 100%
Tools
Shift constrains angle · Alt-click selects inside a group · Ctrl+wheel zooms
Arrows
Jogs A jogged leader steps around whatever is in the way instead of crossing it.
Style
Text
Loading…
·
Code Edits here redraw automatically. Selecting a shape highlights its markup.
1
Export
SVG Syntax Reference

Everything below is a working example. Each card shows the markup on the right and exactly that markup rendered on the left, and Load drops the snippet straight into the editor above so you can take it apart. Read it as a key to the code pane: once you can name the eight or so elements and the dozen attributes that do the real work, an unfamiliar SVG stops being a wall of numbers.

The document

An SVG is a coordinate system with shapes painted into it. Almost everything else is a variation on that: the root element declares the coordinate system, elements declare geometry in those coordinates, and attributes decide how each one is painted.

The <svg> root
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 90">
  <rect x="10" y="10" width="220" height="70" fill="#dce7ea" stroke="#434343" stroke-width="2" />
</svg>
The wrapper every document needs. xmlns is required for a standalone .svg file (it can be omitted when the markup is inlined directly in HTML). viewBox declares the internal coordinate system as min-x min-y width height. width and height are optional display sizes — leave them off and the drawing scales to fill whatever box it is put in.
Coordinates run down, not up
<rect x="0" y="0" width="240" height="90" fill="#f5f8f9" />
<line x1="0" y1="2" x2="150" y2="2" stroke="#c0392b" stroke-width="4" />
<line x1="2" y1="0" x2="2" y2="70" stroke="#0b6fa4" stroke-width="4" />
<text x="14" y="22" font-size="13" fill="#c0392b">x increases right</text>
<text x="14" y="40" font-size="13" fill="#0b6fa4">y increases DOWN</text>
<circle cx="185" cy="62" r="5" fill="#434343" />
<text x="152" y="82" font-size="12">185, 62</text>
The origin is the top-left corner and y grows downward, which is the single most common source of confusion for anyone coming from graph paper or CAD. A point at y="10" sits above a point at y="60".
Groups — <g>
<g fill="none" stroke="#0b6fa4" stroke-width="3">
  <circle cx="40" cy="45" r="22" />
  <rect x="70" y="23" width="44" height="44" />
</g>
<g fill="none" stroke="#c0392b" stroke-width="3" transform="translate(120,0)">
  <circle cx="40" cy="45" r="22" />
  <rect x="70" y="23" width="44" height="44" />
</g>
A <g> does two jobs: it hands its painting attributes down to every child, so stroke is written once instead of on each shape, and it applies one transform to the whole set. Both groups here contain identical geometry; only the group attributes differ.
Reuse — <defs> and <use>
<defs>
  <g id="ref-bolt">
    <circle cx="0" cy="0" r="10" fill="#dce7ea" stroke="#434343" stroke-width="2" />
    <line x1="-6.5" y1="0" x2="6.5" y2="0" stroke="#434343" stroke-width="2" />
  </g>
</defs>
<use href="#ref-bolt" x="45" y="45" />
<use href="#ref-bolt" x="120" y="45" />
<use href="#ref-bolt" x="195" y="45" />
Anything inside <defs> is defined but not drawn. <use> then stamps it wherever you want, with x and y acting as a translate. Edit the definition and every copy follows. Older files use xlink:href instead of href; both still work.
Stylesheets and classes
<style>
  .ref-part { fill: #dce7ea; stroke: #434343; stroke-width: 2; }
  .ref-ctr  { stroke: #c0392b; stroke-width: 1; stroke-dasharray: 12 3 2 3; }
</style>
<rect class="ref-part" x="30" y="20" width="180" height="50" rx="6" />
<line class="ref-ctr" x1="10" y1="45" x2="230" y2="45" />
An SVG can carry its own <style> block, and CSS properties beat presentation attributes. This is the tidiest way to keep a family of parts consistent. The catch: a class only means something if the stylesheet travels with the markup, so a snippet that relies on classes defined in the host page will look wrong on its own.
Accessible titles
<title>Pressure vessel nozzle</title>
<desc>Section through the reinforcing pad</desc>
<rect x="30" y="24" width="180" height="42" fill="#dce7ea" stroke="#434343" stroke-width="2" />
<text x="120" y="82" font-size="12" text-anchor="middle">hover the shape to see the title</text>
<title> as the first child gives the drawing an accessible name and a browser tooltip; <desc> adds a longer description. On the root element, role="img" with aria-label does the same job for screen readers in one line.

Basic shapes

Six elements cover most geometry. Every one of them takes the same painting attributes, so once you know fill and stroke the only thing that changes between them is how the outline is described.

<line>
<line x1="20" y1="70" x2="220" y2="20" stroke="#434343" stroke-width="3" />
Two endpoints, x1 y1 to x2 y2. A line has no interior, so fill does nothing — forget stroke and nothing appears at all, which is the usual reason a line seems to be missing.
<rect>
<rect x="20" y="20" width="90" height="50" fill="#dce7ea" stroke="#434343" stroke-width="2" />
<rect x="130" y="20" width="90" height="50" rx="14" fill="none" stroke="#c0392b" stroke-width="2" />
x y is the top-left corner, not the centre. rx (and optionally ry) rounds the corners; give only rx and the corners are circular.
<circle>
<circle cx="70" cy="45" r="32" fill="#dce7ea" stroke="#434343" stroke-width="2" />
<circle cx="170" cy="45" r="32" fill="none" stroke="#c0392b" stroke-width="2" stroke-dasharray="5 4" />
Positioned by its centre cx cy with radius r — unlike <rect>, which uses a corner. Mixing the two up is what makes shapes land half a width away from where you expected.
<ellipse>
<ellipse cx="120" cy="45" rx="100" ry="32" fill="#dce7ea" stroke="#434343" stroke-width="2" />
A circle with independent radii. rx is the horizontal semi-axis, ry the vertical one.
<polyline>
<polyline points="15,70 60,25 105,60 150,20 195,55 230,30"
          fill="none" stroke="#0b6fa4" stroke-width="3" />
A run of connected segments through a list of x,y pairs. The path is left open. Set fill="none" explicitly — the default fill is black, and a polyline that inherits it renders as a solid blob between its first and last points.
<polygon>
<polygon points="120,12 168,44 150,80 90,80 72,44"
         fill="#dce7ea" stroke="#434343" stroke-width="2" />
The same points list as a polyline, but the shape is closed back to the first point automatically. This is what the arrowheads in this editor are built from.

Paths

<path> can draw anything the other shapes can and a great deal they cannot. Its whole geometry lives in one d attribute, written as a string of single-letter commands followed by numbers. An uppercase command takes absolute coordinates; the lowercase version of the same letter takes coordinates relative to the current point.

M, L and Z
<path d="M 20 70 L 70 20 L 130 55 L 200 15 L 220 70 Z"
      fill="#dce7ea" stroke="#434343" stroke-width="2" />
M moves the pen without drawing, L draws a straight line to a point, Z closes the shape back to the last M. Every path starts with M. Several M commands in one d make several subpaths.
H and V shorthands
<path d="M 30 25 H 210 V 65 H 30 Z"
      fill="none" stroke="#0b6fa4" stroke-width="3" />
H draws a horizontal line to an x coordinate and V a vertical line to a y coordinate, each taking one number instead of two. Handy for rectangular outlines and dimension witness lines.
Cubic Bézier — C and S
<g stroke="#9fb3ba" stroke-width="1" stroke-dasharray="3 2" fill="#9fb3ba">
  <line x1="20" y1="72" x2="55" y2="12" />
  <line x1="135" y1="48" x2="105" y2="12" />
  <circle cx="55" cy="12" r="3.2" />
  <circle cx="105" cy="12" r="3.2" />
</g>
<path d="M 20 72 C 55 12, 105 12, 135 48 S 205 84, 228 26"
      fill="none" stroke="#c0392b" stroke-width="3" />
C x1 y1, x2 y2, x y takes two control points and an endpoint; the curve is pulled toward the controls without touching them (shown dashed). S continues with a smooth joint, reflecting the previous control point so you only supply the second control and the end.
Quadratic Bézier — Q and T
<g stroke="#9fb3ba" stroke-width="1" stroke-dasharray="3 2" fill="#9fb3ba">
  <line x1="20" y1="70" x2="75" y2="8" />
  <line x1="75" y1="8" x2="130" y2="55" />
  <circle cx="75" cy="8" r="3.2" />
</g>
<path d="M 20 70 Q 75 8, 130 55 T 228 42"
      fill="none" stroke="#0b6fa4" stroke-width="3" />
A single control point instead of two: Q cx cy, x y. T is its smooth continuation and needs only the endpoint. Cheaper to write than a cubic and enough for most gentle curves.
Arcs — A
<path d="M 20 72 A 45 45 0 0 1 110 72" fill="none" stroke="#434343" stroke-width="3" />
<path d="M 140 72 A 45 45 0 1 1 220 72" fill="none" stroke="#c0392b" stroke-width="3" />
<text x="20" y="20" font-size="11">large-arc 0</text>
<text x="140" y="20" font-size="11">large-arc 1</text>
A rx ry rotation large-arc-flag sweep-flag x y. The two flags are the confusing part: large-arc picks the long way round rather than the short way, and sweep picks clockwise rather than anticlockwise. Four arcs fit any two points and a radius; the flags say which one you meant.
Absolute against relative
<path d="M 20 30 l 40 0 l 0 30 l 40 0" fill="none" stroke="#0b6fa4" stroke-width="3" />
<path d="M 130 30 L 170 30 L 170 60 L 210 60" fill="none" stroke="#c0392b" stroke-width="3" />
<text x="20" y="82" font-size="11" fill="#0b6fa4">lowercase = relative</text>
<text x="130" y="82" font-size="11" fill="#c0392b">uppercase = absolute</text>
These two paths draw the identical shape. Relative commands are compact and let you move a whole path by editing only its first M; absolute commands are easier to read and to edit by hand. This editor writes absolute coordinates, and translates them properly when you drag a path.
CommandNameWhat it does
M x ymovetoLift the pen and start here. Every d begins with one.
L x ylinetoStraight line to a point.
H xhorizontal linetoStraight line to an x coordinate, y unchanged.
V yvertical linetoStraight line to a y coordinate, x unchanged.
C x1 y1 x2 y2 x ycubic BézierTwo control points, then the endpoint.
S x2 y2 x ysmooth cubicFirst control point mirrored from the previous curve.
Q x1 y1 x yquadratic BézierOne shared control point, then the endpoint.
T x ysmooth quadraticControl point mirrored from the previous curve.
A rx ry rot laf sf x yelliptical arcRadii, x-axis rotation, large-arc flag, sweep flag, endpoint.
ZclosepathStraight line back to the last M. Takes no numbers.

Every command has a lowercase twin that reads its numbers as offsets from the current point rather than as absolute coordinates. Commas and whitespace between numbers are interchangeable, and a repeated command letter may be omitted — L 10 20 L 30 40 and L 10 20 30 40 are the same path.

Fill, stroke and colour

Every shape is painted twice: the interior with fill, the outline with stroke. Both default to something surprising — fill is black and stroke is none — so a shape you expected to be an outline arrives as a silhouette until you say otherwise.

fill and stroke
<rect x="12" y="18" width="66" height="46" fill="#dce7ea" />
<rect x="88" y="18" width="66" height="46" fill="none" stroke="#434343" stroke-width="3" />
<rect x="164" y="18" width="66" height="46" fill="#dce7ea" stroke="#c0392b" stroke-width="3" />
<g font-size="11" text-anchor="middle" fill="#5a6168">
  <text x="45" y="80">fill only</text>
  <text x="121" y="80">stroke only</text>
  <text x="197" y="80">both</text>
</g>
Colours may be named (red), hex (#c0392b), rgb(192 57 43), or the keyword none, which paints nothing at all and is not the same as transparent white. currentColor picks up the inherited CSS color, which is how an icon takes on the colour of the text around it.
Line caps and joins
<g fill="none" stroke="#434343" stroke-width="13">
  <path d="M 22 20 L 70 20" stroke-linecap="butt" />
  <path d="M 22 48 L 70 48" stroke-linecap="round" />
  <path d="M 22 76 L 70 76" stroke-linecap="square" />
</g>
<g fill="none" stroke="#0b6fa4" stroke-width="12">
  <path d="M 110 78 L 132 22 L 154 78" stroke-linejoin="miter" />
  <path d="M 168 78 L 190 22 L 212 78" stroke-linejoin="round" />
</g>
<g font-size="10" fill="#5a6168">
  <text x="80" y="24">butt</text><text x="80" y="52">round</text><text x="80" y="80">square</text>
</g>
stroke-linecap shapes the ends: butt stops dead on the endpoint, while round and square overshoot by half the stroke width — worth knowing when a line has to stop exactly on a boundary. stroke-linejoin shapes the corners, with miter sharp and round softened.
Dash patterns
<g fill="none" stroke="#434343" stroke-width="2.5">
  <line x1="15" y1="14" x2="225" y2="14" />
  <line x1="15" y1="32" x2="225" y2="32" stroke-dasharray="8 4" />
  <line x1="15" y1="50" x2="225" y2="50" stroke-dasharray="1 5" stroke-linecap="round" />
  <line x1="15" y1="68" x2="225" y2="68" stroke-dasharray="16 4 3 4" />
  <line x1="15" y1="86" x2="225" y2="86" stroke-dasharray="16 4 3 4" stroke-dashoffset="10" />
</g>
stroke-dasharray alternates dash and gap lengths. One value repeats it for both; an odd-length list is repeated to make it even. 16 4 3 4 is the classic centre line. stroke-dashoffset slides the pattern along, which is how you stop a dash from landing awkwardly on a corner.
Opacity
<circle cx="62" cy="45" r="32" fill="#0b6fa4" />
<circle cx="104" cy="45" r="32" fill="#c0392b" fill-opacity="0.5" />
<circle cx="190" cy="45" r="30" fill="#0b6fa4" stroke="#434343" stroke-width="8" opacity="0.35" />
fill-opacity and stroke-opacity fade one channel each. opacity fades the element as a finished picture — note the right-hand circle, where the fill does not show through its own stroke because the two are composited first and faded afterwards.
fill-rule
<path d="M 62 12 L 98 78 L 26 78 Z M 62 34 L 80 70 L 44 70 Z" fill="#0b6fa4" fill-rule="nonzero" />
<path d="M 180 12 L 216 78 L 144 78 Z M 180 34 L 198 70 L 162 70 Z" fill="#0b6fa4" fill-rule="evenodd" />
<g font-size="11" text-anchor="middle" fill="#5a6168">
  <text x="62" y="90">nonzero</text><text x="180" y="90">evenodd</text>
</g>
When a path encloses itself, this decides what counts as inside. nonzero (the default) fills a hole only when the inner loop winds the opposite way; evenodd alternates on every crossing regardless of direction. Both paths here are identical — only the rule differs. This is the fix when a letter or washer refuses to show its hole.

Text

SVG text is a shape like any other: it is positioned by a point on its baseline rather than by a box, and it does not wrap. Anything resembling a paragraph has to be broken into lines by hand.

<text> and text-anchor
<line x1="120" y1="6" x2="120" y2="86" stroke="#c0392b" stroke-width="1" stroke-dasharray="3 3" />
<text x="120" y="26" font-size="14" text-anchor="start">start</text>
<text x="120" y="54" font-size="14" text-anchor="middle">middle</text>
<text x="120" y="80" font-size="14" text-anchor="end">end</text>
All three labels are anchored to the same x="120". text-anchor decides which part of the string lands there: start (the default), middle or end. Centring a label under a dimension needs middle, not arithmetic.
Baselines
<line x1="10" y1="45" x2="230" y2="45" stroke="#c0392b" stroke-width="1" />
<text x="14" y="45" font-size="13">auto</text>
<text x="66" y="45" font-size="13" dominant-baseline="middle">middle</text>
<text x="142" y="45" font-size="13" dominant-baseline="hanging">hanging</text>
y is the baseline, so text sits above it by default and descenders hang below. dominant-baseline="middle" centres the glyphs on y instead, which is what you want for a label inside a circle or a table cell.
Fonts
<text x="12" y="26" font-size="16" font-family="Roboto, Arial, sans-serif">Roboto 16</text>
<text x="12" y="52" font-size="16" font-family="Georgia, serif" font-style="italic">Georgia italic</text>
<text x="12" y="80" font-size="15" font-family="monospace" font-weight="bold" letter-spacing="1.5">Mono bold</text>
font-size is in user units, so it scales with the drawing. Give font-family a fallback stack: an SVG opened outside your page has no access to a webfont, and a missing family silently becomes the default serif, which changes every label width.
Multiple lines with <tspan>
<text x="14" y="24" font-size="14" fill="#1c2227">
  <tspan x="14" dy="0">SVG never wraps text.</tspan>
  <tspan x="14" dy="20">Every line is its own <tspan fill="#c0392b" font-weight="bold">tspan</tspan>,</tspan>
  <tspan x="14" dy="20">repeating x and stepping dy.</tspan>
</text>
A <tspan> restates x and steps down by dy to make the next line. Nested inline, it restyles a few words without breaking the run. This is exactly what the Text tool writes when you type a line break.
Rotated labels
<text x="30" y="86" font-size="13" transform="rotate(-90 30 86)">Vertical</text>
<text x="86" y="62" font-size="13" transform="rotate(-22 86 62)">Angled label</text>
<circle cx="30" cy="86" r="2.5" fill="#c0392b" /><circle cx="86" cy="62" r="2.5" fill="#c0392b" />
Rotate about the anchor point itself — rotate(-90 30 86) where the two extra numbers repeat the text’s own x y (marked red). Give rotate() only an angle and it pivots about the origin instead, flinging the label off the canvas.
Text on a path
<defs>
  <path id="ref-curve" d="M 15 72 Q 120 6 225 72" />
</defs>
<path d="M 15 72 Q 120 6 225 72" fill="none" stroke="#cfd8dc" stroke-width="1" />
<text font-size="14" fill="#0b6fa4">
  <textPath href="#ref-curve" startOffset="6%">Text flowing along a curve</textPath>
</text>
Put a <textPath> inside a <text> and point it at a path by id. The path itself is not drawn unless you also render it, as the grey guide here is. startOffset slides the string along.

Transforms

A transform changes the coordinate system an element is drawn in rather than its written geometry. Several can be listed together and they apply right to left — the last one written is the first one felt.

The four transforms
<g fill="#dce7ea" stroke="#434343" stroke-width="2">
  <rect x="10" y="28" width="40" height="34" />
  <rect x="10" y="28" width="40" height="34" transform="translate(56,0) rotate(20 30 45)" />
  <rect x="10" y="28" width="40" height="34" transform="translate(104,10) scale(1.15)" />
  <rect x="10" y="28" width="40" height="34" transform="translate(178,0) skewX(-16)" />
</g>
<g font-size="10" fill="#5a6168" text-anchor="middle">
  <text x="30" y="84">none</text><text x="86" y="84">rotate</text>
  <text x="140" y="84">scale</text><text x="196" y="84">skewX</text>
</g>
translate(dx,dy) moves, scale(k) or scale(kx,ky) resizes, rotate(deg) turns, skewX/skewY(deg) slants. matrix(a b c d e f) expresses any combination in one go. Note that scale multiplies the stroke width too.
Rotating about a point
<circle cx="120" cy="45" r="34" fill="none" stroke="#cfd8dc" stroke-width="1" />
<g stroke="#0b6fa4" stroke-width="3" fill="none">
  <line x1="120" y1="45" x2="120" y2="11" />
  <line x1="120" y1="45" x2="120" y2="11" transform="rotate(60 120 45)" />
  <line x1="120" y1="45" x2="120" y2="11" transform="rotate(120 120 45)" />
  <line x1="120" y1="45" x2="120" y2="11" transform="rotate(180 120 45)" />
  <line x1="120" y1="45" x2="120" y2="11" transform="rotate(240 120 45)" />
  <line x1="120" y1="45" x2="120" y2="11" transform="rotate(300 120 45)" />
</g>
<circle cx="120" cy="45" r="3" fill="#c0392b" />
One spoke, drawn six times. rotate(angle cx cy) takes the centre as two extra numbers; without them the pivot is the origin at the top-left, which is almost never what you want. Positive angles turn clockwise, because y points down.
Strokes that ignore scale
<g fill="none" stroke="#434343" stroke-width="2">
  <rect x="8" y="30" width="32" height="32" />
  <g transform="translate(56,12) scale(1.7)"><rect x="0" y="0" width="32" height="32" /></g>
  <g transform="translate(150,12) scale(1.7)"><rect x="0" y="0" width="32" height="32" vector-effect="non-scaling-stroke" /></g>
</g>
<g font-size="10" fill="#5a6168" text-anchor="middle">
  <text x="24" y="94">1&times;</text><text x="83" y="94">scaled</text><text x="177" y="94">non-scaling-stroke</text>
</g>
Scaling a group thickens its outlines along with everything else. vector-effect="non-scaling-stroke" holds the stroke at its written width no matter what transform is above it — the usual fix for hairlines and leader lines in a scaled assembly.

Arrows and annotation

There is no arrow element. An arrow is a line plus a head you draw yourself, or a line plus a marker the renderer places for you. Both are shown here, along with the two annotation idioms this site uses most.

Arrow as a plain group
<g>
  <line x1="20" y1="45" x2="196" y2="45" stroke="#c0392b" stroke-width="2" />
  <polygon points="214,45 194,37.5 194,52.5" fill="#c0392b" />
</g>
A shaft stopping just short of the tip, plus a triangle. Nothing to reference, nothing to collide with, and every number is visible and editable. This is what the Arrow tool writes. Stop the shaft before the tip so its square end cannot poke through a light-coloured head.
Arrow with a <marker>
<defs>
  <marker id="ref-tip" viewBox="0 0 10 10" refX="9" refY="5"
          markerWidth="6" markerHeight="6" orient="auto-start-reverse">
    <path d="M 0 0 L 10 5 L 0 10 z" fill="#0b6fa4" />
  </marker>
</defs>
<line x1="20" y1="26" x2="212" y2="26" stroke="#0b6fa4" stroke-width="2" marker-end="url(#ref-tip)" />
<path d="M 20 72 Q 120 48 212 68" fill="none" stroke="#0b6fa4" stroke-width="2" marker-end="url(#ref-tip)" />
A marker is attached with marker-start, marker-mid or marker-end and follows the line direction automatically, which is why it works on the curve too. The cost is the id: paste two marker-based snippets into one page and the duplicate ids silently make one arrow adopt the other’s colour.
Dimension line
<g stroke="#5a6168" stroke-width="1" fill="none">
  <line x1="40" y1="18" x2="40" y2="74" />
  <line x1="200" y1="18" x2="200" y2="74" />
  <line x1="40" y1="62" x2="200" y2="62" />
</g>
<polygon points="40,62 55,58 55,66" fill="#5a6168" />
<polygon points="200,62 185,58 185,66" fill="#5a6168" />
<text x="120" y="54" font-size="13" text-anchor="middle" fill="#1c2227">e = 1.000&quot;</text>
Two witness lines, a dimension line between them, inward heads and a centred label sitting just above. Keep the dimension stroke lighter than the part outline so it reads as annotation. The Dimension tool draws this pattern, ticks included.
Leader with a landing
<text x="232" y="18" font-size="12" text-anchor="end" fill="#0b6fa4">Bushed bore</text>
<g stroke="#0b6fa4" stroke-width="1.6" fill="none">
  <line x1="232" y1="24" x2="178" y2="24" />
  <line x1="178" y1="24" x2="82" y2="61" />
</g>
<polygon points="68,67 81.2,56.6 84.8,65.9" fill="#0b6fa4" />
<circle cx="60" cy="70" r="16" fill="none" stroke="#cfd8dc" stroke-width="6" />
A horizontal landing under the label, then a straight run to the feature, then the head. The landing is what stops the text colliding with the line and keeps a column of callouts aligned. Pick the Leader arrow style and drag from the feature outward.

Gradients and patterns

A fill does not have to be a flat colour. Anything defined in <defs> with an id can be referenced as fill="url(#that-id)", including gradients and repeating tiles.

linearGradient
<defs>
  <linearGradient id="ref-lg" x1="0" y1="0" x2="1" y2="0">
    <stop offset="0%" stop-color="#61828a" />
    <stop offset="55%" stop-color="#a9cccf" />
    <stop offset="100%" stop-color="#f2f7f8" />
  </linearGradient>
</defs>
<rect x="15" y="18" width="210" height="54" fill="url(#ref-lg)" stroke="#434343" stroke-width="1" />
x1 y1 to x2 y2 set the gradient axis, by default in fractions of the shape’s own bounding box, so 0,0 to 1,0 means left to right whatever the size. Each <stop> places a colour at a percentage along it.
radialGradient
<defs>
  <radialGradient id="ref-rg" cx="36%" cy="30%" r="72%">
    <stop offset="0%" stop-color="#ffffff" />
    <stop offset="100%" stop-color="#61828a" />
  </radialGradient>
</defs>
<circle cx="120" cy="45" r="38" fill="url(#ref-rg)" stroke="#434343" stroke-width="1" />
Same idea radiating from a centre. Offsetting cx cy away from the middle puts the highlight off-centre, which is all it takes to make a circle read as a sphere.
pattern — section hatching
<defs>
  <pattern id="ref-hatch" width="8" height="8" patternUnits="userSpaceOnUse"
           patternTransform="rotate(45)">
    <line x1="0" y1="0" x2="0" y2="8" stroke="#434343" stroke-width="1.2" />
  </pattern>
</defs>
<rect x="15" y="18" width="92" height="54" fill="url(#ref-hatch)" stroke="#434343" stroke-width="2" />
<circle cx="178" cy="45" r="30" fill="url(#ref-hatch)" stroke="#434343" stroke-width="2" />
A tile repeated across whatever it fills. patternUnits="userSpaceOnUse" fixes the tile in drawing units so the spacing stays put regardless of the shape’s size, and patternTransform="rotate(45)" gives the 45° run of a section view — far less work than drawing and clipping dozens of individual hatch lines.

Clipping and masking

Two ways to show only part of something. Clipping is a hard-edged cookie cutter; masking uses brightness, so it can fade.

clipPath
<defs>
  <clipPath id="ref-clip"><circle cx="120" cy="45" r="34" /></clipPath>
</defs>
<g clip-path="url(#ref-clip)">
  <rect x="70" y="8" width="52" height="74" fill="#0b6fa4" />
  <rect x="122" y="8" width="52" height="74" fill="#c0392b" />
</g>
<circle cx="120" cy="45" r="34" fill="none" stroke="#434343" stroke-width="1.5" />
Everything outside the clip shape is cut away with a hard edge. The clip geometry is never painted itself — the thin outline here is a second circle drawn on top to show where the boundary is.
mask
<defs>
  <mask id="ref-mask">
    <rect x="0" y="0" width="240" height="90" fill="#fff" />
    <text x="120" y="62" font-size="46" font-weight="bold" text-anchor="middle" fill="#000">CUT</text>
  </mask>
</defs>
<rect x="0" y="0" width="240" height="90" fill="#f5f8f9" />
<rect x="15" y="12" width="210" height="66" fill="#61828a" mask="url(#ref-mask)" />
Inside a mask, white keeps and black hides, with greys giving partial transparency. That makes knock-out text and soft fades possible in a way clipPath cannot manage, at the price of a little more machinery.
<image>
<image href="img/blueK.webp" x="20" y="14" width="98" height="60"
       preserveAspectRatio="xMidYMid meet" />
<rect x="20" y="14" width="98" height="60" fill="none" stroke="#434343" stroke-width="1" />
<text x="130" y="40" font-size="12" fill="#5a6168">raster inside</text>
<text x="130" y="56" font-size="12" fill="#5a6168">a vector document</text>
A bitmap placed in the coordinate system like any other shape. preserveAspectRatio controls the fit inside the box, exactly as it does on the root <svg>. Note that a relative href only resolves where the file sits — export the SVG somewhere else and the image goes missing, so embed a data: URI when the file has to travel.

How this editor is put together

The code pane and the canvas are two views of one document. Drawing an arrow rewrites the code; editing the code redraws the canvas. Nothing is hidden in a private scene graph, so what you see in the code pane is exactly what you get when you copy it — there is no separate project format and no export step that changes the markup.

That matters because the code is the deliverable. Most drawings on this site are generated by scripts and hand-tuned afterwards, so the useful output is a clean snippet you can paste back into a page or a generator, not a binary file. Raster export is there when you need a picture, but the SVG code is the primary format.

Undo works across both halves. Every committed change — a drag, a restyle, a paste into the code pane — pushes a snapshot of the source onto one shared history, so Ctrl+Z always steps back one change no matter which pane you made it in. Up to 150 steps are kept.

Loading a drawing

Use Open, drop an .svg file straight onto the canvas, or paste markup into the code pane. Fragments are accepted too — paste a handful of bare <path> or <line> elements with no wrapper and they are wrapped in an <svg> for you.

Awkward source is handled in stages. The strict XML parser runs first; if it rejects the input, the browser's far more forgiving HTML parser gets a second pass, which recovers unquoted attributes, bare ampersands and unclosed tags. If a document arrives with no viewBox, one is synthesized from the width and height attributes, or measured from the content when those are missing too. Existing <style> blocks, <defs>, gradients, groups and transforms all survive the round trip untouched.

Imported markup is sanitized. An SVG is a live document, and one loaded from elsewhere could carry <script> elements, on* event handlers or javascript: links that would run in this page. Those are stripped before anything is rendered, and the status line tells you how many were removed. Everything else is left exactly as written.

Drawing and selecting

Click a shape to select it. A click selects the outermost group, so an arrow or a labelled assembly moves as a unit; Alt-click drills through to the exact element underneath for surgery on an imported drawing. Thin lines and unfilled shapes are still selectable — if nothing is painted under the cursor, the smallest bounding box containing that point wins.

Drag a selection to move it. Lines and arrows get endpoint handles; rectangles, ellipses, circles and images get corner handles. Arrow keys nudge by one unit, Shift plus an arrow key by ten, and with Snap on, by one grid step.

Moves rewrite the geometry attributes rather than wrapping everything in a transform, so a rectangle you drag stays a plain <rect x y width height> in the code. Path data is translated command by command. Only elements whose position cannot be expressed in their own attributes pick up a transform, and repeated moves merge into a single translate instead of stacking up.

Arrows

Arrows are emitted as a self-contained <g> of ordinary shapes rather than as a marker in <defs>. Markers are tidier in isolation but they carry an id reference, and the moment you paste two marker-based snippets into the same page the ids collide and one of them silently changes colour. A group of plain lines and polygons pastes anywhere, in any order, and always looks the same.

Head length and width scale with the stroke width, so a heavier arrow does not end up with a pinhead. Drag with Shift held to constrain the shaft to 15° steps. Select an existing arrow and the inspector gains its own style, colour and weight controls, which rebuild the geometry in place rather than layering attributes on top of it.

StyleWhat it drawsWhere it fits
Solid headShaft with a filled triangular head.Load and reaction arrows. The default for force vectors on a schematic.
Open headFull-length shaft with two stroked barbs, no fill.Lighter annotation where a solid head would read as a load.
Swept barbFilled head with a notched tail, swept back.Flow direction and motion, where a plain triangle looks blunt.
Double headFilled heads at both ends.Ranges, spans, and any two-way relationship.
DimensionThin shaft, heads at both ends, perpendicular witness ticks.Dimensioning a drawing. Thinner than the others by design so it recedes.
LeaderHorizontal landing, a straight run, then a head at the tip.Callouts pointing from a label into a feature.

Jogged lines

A leader running straight from a label to a feature often has to cross something on the way — another dimension, a hatched area, a second leader. A jogged line steps around it instead. Pick the Jog tool, choose a shape, and drag from one end to the other; the intermediate vertices are placed for you and move with the endpoints.

ShapePath it takesWhere it fits
Elbow, across firstHorizontal from the start, then vertical to the end.A label sitting to one side of the feature it points at.
Elbow, down firstVertical from the start, then horizontal to the end.A label directly above or below the feature.
Z-step, acrossHorizontal, a vertical step at the midpoint, then horizontal again.Two features at different heights that each need a level run into them.
Z-step, downThe same step turned through ninety degrees.Routing around something sitting directly in the path.
ZigzagA straight run carrying the standard jog symbol at its midpoint.A dimension whose length is not drawn to scale — the conventional way to say so.

Tick arrow head to finish the last segment with a head, which turns any of them into a jogged leader. Like arrows, a jog is written out as a plain <polyline> — plus a <polygon> for the head — inside a <g>, so every vertex stays visible and editable in the code. Both endpoints get drag handles, and Shift while dragging one constrains that end to 15° steps.

Text

Pick the Text tool and click to place a label, or double-click any existing <text> to edit it in place. Multi-line content is written out as <tspan> children with a line-height dy, because SVG has no automatic wrapping — a line break has to become a real element. Font size, family, weight and anchor are set on the toolbar for new text and in the inspector for existing text.

The inspector

The Selection panel gives the usual controls — stroke, fill, width, dash, opacity, and the geometry attributes for whichever element type is selected. Colours can be set from the swatch, typed as any CSS colour, or switched to none.

Below those is an All attributes table listing every attribute actually present on the element, editable by name and value, with rows you can add or remove. That is the escape hatch that keeps an unfamiliar drawing workable: attributes this inspector has never heard of — mask, clip-path, vector-effect, marker-end, anything from another tool's export — are all still editable, because nothing is filtered out on the way in.

Background

There are two independent backgrounds, and the distinction is worth keeping straight.

The document background lives in the Document panel. Transparent means no background element at all, which is what you usually want for a diagram that will sit on a page of unknown colour. Solid colour inserts a <rect id="kc-bg"> covering the viewBox as the first child, so it is part of the exported code and part of any raster export.

The backdrop on the View bar changes only what you see while editing. A checkerboard makes transparency obvious; the dark option is the quickest way to check that a drawing with light strokes has not gone invisible against white. It is never exported.

viewBox and trimming

The Document panel edits the viewBox and the width/height attributes directly. Fit viewBox measures the drawn content, ignoring the background rectangle, and rewrites the viewBox to bound it with the padding you specify. That is the fix for a drawing surrounded by dead space — it changes the crop, not the scale, so nothing inside moves relative to anything else.

Export

Choose a format, then Copy code or Download (also Ctrl+S). The pixel size box applies to raster formats only; vector output is resolution independent and ignores it.

FormatNotes
SVG code — formattedIndented, one element per line. The primary output: paste it into a page, a template, or back into a generator script.
SVG code — minifiedSame document with the whitespace and comments stripped. Use it when the markup is going inline into HTML.
Data URIPercent-encoded data:image/svg+xml string for a CSS background-image or an <img src>.
PNGRasterized at the width and height shown. Transparency is preserved unless the document has a background rectangle.
JPEGHas no alpha channel, so the image is flattened onto the background colour first. Quality 0.92.
WebPSmaller than PNG at the same quality, with alpha. Not every browser can encode it; if yours cannot, the status line says so.

Raster export runs entirely in your browser through a canvas. Nothing is uploaded, and the drawing never leaves the page.

Keyboard

KeyAction
Ctrl+ZUndo
Ctrl+Y or Ctrl+Shift+ZRedo
Ctrl+SDownload in the selected export format
Ctrl+DDuplicate the selection, offset by 10 units
V H A D J L R O TSelect, Pan, Arrow, Dimension, Jog, Line, Rectangle, Ellipse, Text
Delete or BackspaceDelete the selection
EscapeClear the selection and return to the Select tool
Arrow keysNudge one unit — ten with Shift, one grid step with Snap on
Shift while drawingConstrain arrows and lines to 15° steps — so 30°, 45° and 60° all land exactly — and rectangles and ellipses to square. Also works when dragging an endpoint handle.
Alt-clickSelect the element inside a group instead of the group
Ctrl+wheelZoom about the cursor. A plain wheel is left alone so the page still scrolls.
Middle-dragPan, from any tool

Shortcuts are ignored while you are typing in a text box, so R in the code pane inserts an R rather than switching tools.