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
| Where | Looks like | Strength |
|---|---|---|
| Presentation attribute | <circle fill="teal"/> | Weakest: any CSS rule that selects the element beats it |
A rule in a <style> element or a style sheet | circle { 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 !important | circle { 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.
| Kind | Examples | In CSS? |
|---|---|---|
| Paint | fill, stroke, stroke-width, stroke-dasharray, stroke-linecap, opacity, fill-opacity, paint-order | Yes |
| Text | font-family, font-size, font-weight, text-anchor, dominant-baseline, letter-spacing | Yes |
| Effects | filter, clip-path, mask, mix-blend-mode, transform, transition, animation | Yes |
| Gradient stops | stop-color, stop-opacity | Yes |
| Some geometry | x, y, width, height, cx, cy, r, rx, ry, d | Yes in Chrome. Other software often reads only the attribute |
| Other geometry | points, x1, y1, x2, y2, viewBox, dx, dy, pathLength, gradientTransform | No: attributes only |
| References | href | No: 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 shown | External style sheet loaded? | Page’s own CSS applies? |
|---|---|---|
| Opened directly in the browser | Yes | There is no page |
<object data="file.svg"> | Yes | No |
<img src="file.svg"> or a CSS background | No | No |
| Pasted inline into the HTML | The page’s <link> and <style> apply instead | Yes |
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 nofillattribute and left alone one that hadfill="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 see | Cause | Fix |
|---|---|---|
A CSS rule on svg or g does not change the shapes | The shapes have their own fill attributes, which beat inherited values | Select the shapes themselves: svg path { ... } |
| A CSS rule does nothing at all | The SVG is in an <img> or a background | Inline the SVG, or put the CSS inside the file |
| An attribute seems to be ignored | A style rule sets the same property | Remove the rule, or use the style attribute |
font-size: 20 is ignored | A CSS length needs a unit | font-size: 20px |
| The external style sheet works when the file is opened but not on the page | The 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-wide | Use distinct class names |
| The file will not open after adding CSS | A < 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 copy | Set 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.