g | x | w | all
Bytes Lang Time Link
nan250529T102406ZMiro
nan241022T072418ZHand-E-F
nan230305T165051ZSE - sto
nan230305T162416ZSE - sto
000Coordinates use default 0230305T112748ZSE - sto
nan230304T142420Zccprog
nan230304T142202Zccprog
nan230304T142126Zccprog
nan230304T141931Zccprog
nan230304T141851Zccprog
nan230304T141812Zccprog
nan230304T141733Zccprog
nan230304T141656Zccprog
nan230304T141621Zccprog
nan230303T012348ZParcly T

Use color names where applicable

Save one character by using the colour name red instead of the hex code #f00.

Some other cases where the colour name is equal to or shorter than the hex code are listed here (note that rgba(0,0,0,0) can actually be shortened to #0000, so transparent is not shorter).

Use 12-bit colour codes

Colour codes can be expressed as 12-bit colour codes. There's less resolution, but is fine for most applications.

fill="white"       ==  fill="#FFF"
fill="yellow"      ==  fill="#FF0"
fill="aquamarine"  ~=  fill="#7FC"

You have all of unicode

There's a built-in library of thousands upon thousands of graphic primitives, which cost you just a couple of bytes each, plus an up-front cost of <text></text>

In production SVGs, this is horrible practice since you will of course be at the mercy of font rendering and support, and are mixing up graphics and semantics. But good practice has never stopped code golfing!

Interesting unicode blocks:

unicode

<rect> is never the answer

The equivalent path for a rectangle is always shorter than <rect>

<rect x=5 y=5 width=2 height=1 fill=red />
<path d=M5,5h2v1h-2 fill=red />

If either the width or height is equal to 1 (or otherwise the local line width), <line> is shorter when either the x or y coordinate is 0

One coordinate is 0:

<rect x=5 width=2 height=1 fill=red />
<path d=M5,0h2v1h-2 fill=red />
<line x1=5 x2=7 stroke=red />

Both coordinates are 0:

<rect width=2 height=1 fill=red />
<path d=h2v1h-2 fill=red />
<line x2=2 stroke=red />

Coordinates: use default (0,0) positioning

Transforming the origin is often cheaper than giving explicit coordinates.

<svg viewBox=-4,-4,8,8><circle r=3 fill=red

Path data: Path fills close automatically

If a path has a fill, but no stroke, you can leave out closing the path

M0,0H5V5H0V0Z  ==  M0,0H5V5H0V0  ==  M0,0H5V5H0    for stroke="none" or in a clip-path
                                 ==  M0,0H5V5H0Z   for stroke="<paint>"

But the automatic closing last segment is alway straight, so if you have a curve as the last command, the repetition of the last coordinate might be inevitable

M0,0H5V5Q0,5 0,0Z  ==  M0,0H5V5Q0,5 0,0

...unless you are able to use another point as the start

M0,0H5V5Q0,5 0,0Z  ==  M5,0V5Q0,5 0,0

Path data: Try out the A command

Grafical editors tend to approximate elliptical arcs with (sequences of) cubic Bezier curves. For example, Inkscape perfectly understands

M0,0A10,15-20 0 1 10,12

But move the handle of the end point, even if you immediately move it back to its original position, and suddenly you will have a cubic Bezier.

M 0,0 C 4.3412,1.4378 8.2913,6.1779 10,12

This also happens with quadratic Beziers, with the small difference that the result of the conversion is mathematically equal. Conversions from C to A or C to Q on the other hand are impossible most of the time.

With reasonable rounding, you might (almost) regain the original length, but you will have to test this manually

M0,0C4.3,1.4 8.3,6.2 10,12

But take note: the above arc command has extra opportunities for minification. (This is something that the available tools all miss.) The large_arc and sweep parameters are flags. As they can only be 0 or 1, no space needs to follow them

M0,0A10,15-20 0 1 10,12  ==  M0,0A10,15-20 0110,12

I know this looks strange, but it is really part of the grammar. Note the comma_wsp?

elliptical_arc_argument::=
    number comma_wsp? number comma_wsp? number comma_wsp
    flag comma_wsp? flag comma_wsp? coordinate_pair

Path data: The art of spaces

The most curious term in the BNF grammar is the production comma_wsp?. What it means is: separate numbers with commas and/or whitespace as you wish, or leave them off completely. I'll quote from the spec directly:

The processing of the EBNF must consume as much of a given EBNF production as possible, stopping at the point when a character is encountered which no longer satisfies the production. Thus, in the string "M 100-200", the first coordinate for the "moveto" consumes the characters "100" and stops upon encountering the minus sign because the minus sign cannot follow a digit in the production of a "coordinate". The result is that the first coordinate will be "100" and the second coordinate will be "-200".

Similarly, for the string "M 0.6.5", the first coordinate of the "moveto" consumes the characters "0.6" and stops upon encountering the second decimal point because the production of a "coordinate" only allows one decimal point. The result is that the first coordinate will be "0.6" and the second coordinate will be ".5".

Implied here is that adjecent to command letters no space is needed to separate a number (and commas are forbidden).

The same rules apply to transform functions, but not to the viewBox attribute.

Another small note is about writing SVG content inline in a HTML page. It is legal to leave off the surrounding quotes for an attribute, as long as the attribute contains no whitespace. This applies also to path data, but you might be forced to replace spaces with commas

<path d="M0,0 1,1" />  == <path d=M0,0,1,1 />

Path data: Don't overestimate relative commands

Adobe Illustrator writes all path commands as relative commands, obviously assuming they will lead to shorter strings. While this is true in a lot of cases, most of the time they will lead to nothing, and sometimes even take up extra space

M10,10 15,16  ==  M10,10l5,6
M10,10 25,26  ==  M10,10l15,16
M25,26 10,10  ==  M25,26l-15-16

In an extra note, relative commands are relative not to the previous (control) point, but to the previous stop point, which might be a further distance away

M0,0C0,2 8,10 15,15  != M0,0c0,2 8,8 7,7     WRONG!
                     == M0,0c0,2 8,10 15,15

Path data: Concatenate paths

Path strings can contain multiple substrings. If they

instead of writing two elements, you can just concatenate the path data strings.

<path d="M0,0 5,5" /><path d="M10,10 15,15" />  ==  <path d="M0,0 5,5M10,10 15,15" />

In this case, you could even take advantage of relative commands

<path d="M0,0 5,5m5,5 5,5" />

Path data: Mirror symetrical Bezier curves

There are special commands that mirror the Bezier control points around a stop coordinate.

M0,0Q0,1 1,1 1,2 2,2 2,3 3,3  == M0,0Q0,1 1,1T2,2 3,3
M0,0C0,1 1,2 2,2 3,2 4,3 4,4  == M0,0C0,1 1,2 2,2S4,3 4,4

This even works when mixing quadratic and cubic Beziers:

M0,0C0,1 1,2 2,2Q3,2 3,3  == M0,0C0,1 1,2 2,2T3,3

Path data: Do not repeat command letters

If there are multiple path segments with the same command, you can leave out the letters on the repetitions.

 M0,0Q0,1 1,1Q1,2 2,2  == M0,0Q0,1 1,1 1,2 2,2

Path data: Use H and V instead of L

If your path contains straight lines that are horizontal or vertical, the following sequences are equal:

M0,0L1,0  ==  M0,0H1
M0,0L0,1  ==  M0,0V1

Path data: Use automatic tools

The grammar for the path data microsyntax is especially aimed at a minimal representation, but leaves room for a lot of variants.

Because of that, there are tools available that try to automatically minimize the path data. Notably Adobe Illustrator will automatically try to apply minification on SVG export, and the SVGOMG optimization tool contains a module for the same purpose. The latter one is included in a number of online tools, including for example Figma.

These tools are a good start, but still fall short in some aspects.

Know your transforms

Note: this answer talks about the implementation of transforms described in the SVG 1.1 standard, not the CSS standard.

In SVG there are five main types of affine transforms that can be used in the transform attribute of an object.

My experience with compressing My Little Pony: Friendship Is Magic cutie marks has taught me that in the most general case using matrix is the best. When the transform is only a similarity (angle-preserving) using rotate(a,b,c)scale(s) (transforms are applied right-to-left) is usually better, and for yet more special cases composing two of the non-matrix primitives above is usually better, but never three.


A particularly egregious example of transform compression can be seen in Fluttershy's cutie mark from the relevant section of my Selwyn repository. (The actual file over there duplicates groups because clones don't play well with the path effects of Inkscape; I often use the Envelope Deformation path effect to slightly bend cutie marks to fit the curving flanks of ponies, as can be seen e.g. here.)

<svg viewBox=-50,-50,100,100><g id=a><path d=m15-33.8c-3.8-4,10.2-9.1-3,16.3,14.4-25.5,18.3-11.7,12.6-12.2 fill=none stroke=#69c8c3 stroke-width=.9 /><path d=m17.1-14.3c12.5,13.7-8.1,27-9.7,4-21.6,12.5-21.9-12.6-3-11.6-11.9-12.7,10-31.6,8.4,2.7,28.5-19.9,19.3,2.4,4.3,4.9z fill=#f3b5cf /><path d=m5.9-7.6c4.4-11.9,8.7-17.7,10-17.1,1.3.6-1,7.8-10,17.1z fill=#69c8c3 /></g><use href=#a transform=matrix(.83,0,0,.83,11.9,37.3) /><use href=#a transform=rotate(-65,22.3,22)scale(.94) />

Here one of the cloned butterflies is transformed using the rotate()scale() pattern, but the other clone is not because matrix(.83,0,0,.83,11.9,37.3) is shorter than translate(11.9,37.3)scale(.83).