SVG and CSS: style attribute, style tag and style sheets

The four places an SVG's CSS can live, which one wins, which properties CSS can set, and how an SVG file links an external style sheet. With tested code.

Published

SVG shapes are styled with CSS, using properties such as fill, stroke and stroke-width where HTML uses background and border. The style can be written in four places: as an attribute on the shape, in a style attribute, in a <style> element, or in a separate style sheet. All four do the same job and differ only in which wins when they disagree.

<style>
  .dot { fill: teal; stroke: gold; stroke-width: 4px; }
  .dot:hover { fill: crimson; }
</style>
<svg viewBox="0 0 100 100" width="200">
  <circle class="dot" cx="50" cy="50" r="40"/>
</svg>

A teal circle with a gold outline that turns red under the pointer. The page’s style sheet reaches the circle because the SVG is inline in the page.

Four places to write a style

WhereLooks likeStrength
Presentation attribute<circle fill="teal"/>Weakest: any CSS rule that selects the element beats it
A rule in a <style> element or a style sheetcircle { fill: teal }Ordinary CSS: the more specific selector wins, then the later rule
style attribute<circle style="fill: teal"/>Beats every rule without !important
A rule with !importantcircle { fill: teal !important }Beats the style attribute

The first row is the one peculiar to SVG. An attribute such as fill="teal" is called a presentation attribute. The SVG 2 specification gives it a specificity of 0 and places it before all other author style sheets, so it loses to any rule at all that matches the element, even * { fill: gold }. Tested in Chrome, that rule turned a circle with fill="teal" gold.

It still beats inheritance. A rule that sets fill on the <svg> or on a <g> does not override a fill attribute on a shape inside, because a value set on an element itself always comes before one inherited from a parent. That is why svg { fill: gold } often seems to do nothing to an exported file. Changing SVG colour with CSS deals with that case.

!important cannot be written in a presentation attribute. fill="teal !important" is an invalid value: in Chrome the circle fell back to black.

Which properties CSS can set

Paint and text properties are all CSS properties. Geometry is partly so.

KindExamplesIn CSS?
Paintfill, stroke, stroke-width, stroke-dasharray, stroke-linecap, opacity, fill-opacity, paint-orderYes
Textfont-family, font-size, font-weight, text-anchor, dominant-baseline, letter-spacingYes
Effectsfilter, clip-path, mask, mix-blend-mode, transform, transition, animationYes
Gradient stopsstop-color, stop-opacityYes
Some geometryx, y, width, height, cx, cy, r, rx, ry, dYes in Chrome. Other software often reads only the attribute
Other geometrypoints, x1, y1, x2, y2, viewBox, dx, dy, pathLength, gradientTransformNo: attributes only
ReferenceshrefNo: attribute only

The split in the last rows was read from Chrome with CSS.supports(). A path can even take its outline from a style sheet there, path { d: path("M 10 10 H 90 V 90 H 10 Z") }, though a file meant for anything other than a browser should keep geometry in attributes.

Newer properties exist only in CSS. The SVG 2 specification says new properties will not be given presentation attributes, and mix-blend-mode shows it: mix-blend-mode="multiply" as an attribute did nothing in Chrome, while style="mix-blend-mode: multiply" blended the shape.

Units and values

A presentation attribute is parsed as a CSS value, with one allowance: a plain number is taken as user units. font-size="20" is valid as an attribute. In a style sheet the ordinary CSS rules apply, and text { font-size: 20 } is thrown away because the unit is missing: in Chrome the text stayed at the default 16 pixels. Write font-size: 20px.

stroke-width: 4 is accepted without a unit, since that property allows a number. When in doubt, add px. A px inside an SVG is one unit of the viewBox, not one screen pixel.

Custom properties work in both forms: fill="var(--brand)" and style="fill: var(--brand)" gave the same colour in Chrome.

A style element inside the SVG

An SVG can carry its own <style> element, which keeps the file self-contained:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
  <style>
    .dot { fill: gold; }
    @media (prefers-color-scheme: dark) {
      .dot { fill: teal; }
    }
  </style>
  <circle class="dot" cx="50" cy="50" r="40"/>
</svg>

A gold circle that turns teal when the system is in dark mode. This is the form that works everywhere, including in an <img> tag: the media query was honoured in Chrome when the file was shown as an image.

Two cautions.

Inline in a page, the styles are not private. A <style> element inside an inline SVG applies to the whole page. In a test, circle { fill: gold } inside one SVG coloured the circles of a second SVG, and a p rule in it recoloured an HTML paragraph. Use class names specific to the drawing, such as .logo-dot, when a file may be pasted into a page.

In a standalone file, the content is XML. A < or & in the style sheet breaks the file. The child selector > is safe. If the CSS needs those characters, wrap it: <style><![CDATA[ ... ]]></style>.

Linking an external style sheet from an SVG file

A standalone .svg file can load a separate .css file in three ways. All three loaded the style sheet in Chrome when the file was opened directly.

The XML way, on a line before the <svg> tag:

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="style.css" type="text/css"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
  <circle cx="50" cy="50" r="40"/>
</svg>

An import inside a <style> element:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
  <style>@import url("style.css");</style>
  <circle cx="50" cy="50" r="40"/>
</svg>

Or an HTML <link> element, which must be put in the HTML namespace:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
  <link xmlns="http://www.w3.org/1999/xhtml" rel="stylesheet" href="style.css"/>
  <circle cx="50" cy="50" r="40"/>
</svg>

Without the xmlns on the link it is an unknown SVG element and nothing is loaded.

The catch is where the file is shown:

How the file is shownExternal style sheet loaded?Page’s own CSS applies?
Opened directly in the browserYesThere is no page
<object data="file.svg">YesNo
<img src="file.svg"> or a CSS backgroundNoNo
Pasted inline into the HTMLThe page’s <link> and <style> apply insteadYes

An SVG shown as an image loads nothing from outside itself: no style sheets, no fonts, no pictures. In the test the server received no request for style.css from an <img> tag or from a CSS background, and the circles stayed black. For an SVG used as an image, put the CSS in a <style> element inside the file. Vector editors and cutting software vary in how much CSS they read at all, and plain attributes are the safest form for those.

Styling through use

A <use> element draws a copy of another element. CSS selectors do not see inside the copy: .copy circle matched nothing in Chrome when the circle was drawn by <use class="copy">. Two things do get through:

  • Inheritance. A property set on the <use> is inherited by the copied shapes that have no value of their own. .copy { fill: gold } coloured a circle with no fill attribute and left alone one that had fill="teal".
  • Rules that match the original. A rule such as #pair circle { fill: crimson }, which selects the shape where it is defined, shows in every copy.

So the way to style an icon from a sprite differently in each place is to leave fill off the shapes, or set it to currentColor, and set fill or color on each <use> or its <svg>. SVG g and use explains the element.

Inheritance

Most paint and text properties are inherited. Set on a <g>, they apply to every shape inside that has no value of its own:

<g style="fill: gold; stroke: teal; stroke-width: 4">
  <circle cx="30" cy="50" r="15"/>
  <circle cx="70" cy="50" r="15" fill="crimson"/>
</g>

Both circles get the teal outline. The first is gold and the second keeps its own red. opacity, transform, filter and clip-path are not inherited: they act on the group as a whole.

Common problems

What you seeCauseFix
A CSS rule on svg or g does not change the shapesThe shapes have their own fill attributes, which beat inherited valuesSelect the shapes themselves: svg path { ... }
A CSS rule does nothing at allThe SVG is in an <img> or a backgroundInline the SVG, or put the CSS inside the file
An attribute seems to be ignoredA style rule sets the same propertyRemove the rule, or use the style attribute
font-size: 20 is ignoredA CSS length needs a unitfont-size: 20px
The external style sheet works when the file is opened but not on the pageThe file is shown with <img>Use a <style> element in the file, or <object>
Styles from one inline SVG change another<style> in an inline SVG is page-wideUse distinct class names
The file will not open after adding CSSA < or & in the <style>Wrap the CSS in <![CDATA[ ]]>
.icon path does not style an icon drawn with <use>Selectors do not cross into the copySet fill on the <use> and leave it off the path

Questions

Should I use attributes or CSS? For a file that will be opened in other software, cut or printed, attributes: every program reads them. For a drawing that lives in a web page, classes and a style sheet keep it short and let the page restyle it. The two can be mixed, as long as you remember that CSS wins.

Can CSS animate an SVG? Yes: transitions and keyframes work on these properties. See SVG animation with CSS.

How do I see which rule is winning? Read the value the browser ended up with: getComputedStyle(shape).fill in the console gives the colour in use, whichever of the four places it came from. To see how a file itself sets its colours, open it in the SVG viewer and read the code.

Does CSS inside an SVG work in an email or a document? That depends wholly on the program showing it. Where a file has to look right everywhere, write the styles as attributes on the shapes.