
In HTML documents, we can show, hide or rearrange parts of a page depending on viewport conditions. If the browser window is 480 pixels wide, for example, we can turn our navigation from a horizontal list into a vertical, collapsible one. We can do something similar with SVG and media queries.
This article is an excerpt from Tiffany’s CSS Master, 3rd Edition. Have a look if you want to master other advanced CSS3 features such as Flexbox and Grid!
Beyond using CSS with HTML, we can also use CSS with SVG, or Scalable Vector Graphics. SVG is a markup format for describing flat, two-dimensional images. Because it is a markup language, it has a document object model and can be used with CSS.
With CSS, we can change the look of an SVG in response to user interaction. We can also use the same SVG document in several places, showing or hiding parts of it depending on the viewport width.
Every major browser engine has supported SVG 1.1 for years. Support for SVG 2 features, on the other hand, is still a work in progress. At the time of writing, browser support is limited for some of the topics covered here, and that may have changed by the time you read this. Keep an eye on the Chromium meta-issue Implement SVG2 features to follow the work in Chromium-based browsers. Watch the Support SVG 2 features meta-issue to follow the Firefox implementation, and the WebKit Implement SVG 2 meta-issue for Safari. Issue trackers are not much fun to browse, but for now they are the best way to track SVG 2 support.
Before going any further, though, let’s talk about what SVG is and why you should use it.
Vector images and raster images
Most images used on the web today are raster images, also known as bitmap images. Raster images are made of pixels on a fixed grid, with a set number of pixels per inch. JPEG, WebP, GIF and PNG are all examples of raster images.
Raster images are resolution dependent. A 144 PPI (pixels per inch) PNG image looks great on a device with a 144 PPI display. Shown on a higher-resolution screen (400 PPI), however, that same image can look blurry. Raster images also have fixed dimensions and look their best at their original size. Scaling a 150 by 150 pixel image up to 300 by 300 pixels distorts it.
Instead of using pixels on a grid, vector image formats describe the primitive shapes, circles, rectangles, lines or paths, that make up an image, along with their position in the coordinate system of the document. Vector images are therefore resolution independent and keep their quality whatever the resolution or the dimensions of the screen.
Resolution independence is the biggest advantage of SVG. We can scale images up or down with no loss of quality. The same image looks great on high- and low-PPI devices. That said, SVG is a poor fit for the amount of colour data a photograph needs. It suits drawings and shapes far better. Use it in place of PNG and GIF images, and as a more flexible replacement for icon fonts.
Another advantage of SVG is that it was designed to be used with the other web languages. We can create, edit and manipulate SVG images with JavaScript. Or, as we will see below, we can style and animate SVG with CSS.
Associating CSS with SVG documents
Using CSS with SVG works much like using CSS with HTML. We can apply CSS through the style attribute of an SVG element, group the CSS of a document in a <style> element, or link to an external style sheet. The pros and cons of each method are the same as when using CSS with HTML.
Using the style attribute
Here is a simple SVG document whose code draws a black circle:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"
➥enable-background="new 0 0 200 200">
<circle cx="101.3" cy="96.8" r="79.6" />
</svg>
The image below shows how that code renders in a browser.

Let’s give our circle a pink fill with CSS, using the style attribute:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"
➥enable-background="new 0 0 200 200">
<circle cx="101.3" cy="96.8" r="79.6" style="fill: #f9f" />
</svg>
The result is shown below.

Here is one difference between using CSS with HTML and using it with SVG: the property names. Many CSS properties we use with HTML documents do not work with SVG, and the other way round. We will come back to this later in the chapter.
Using the style attribute is not the best way to work with CSS, of course. It limits how far you can reuse those styles across several elements or documents. Embedded or linked CSS is a better option.
Embedding CSS in SVG documents
Instead of the style attribute, we can use the <style> element:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0
➥ 200 200" enable-background="new 0 0 200 200">
<style type="text/css">
circle {
fill: #0c0;
}
</style>
<circle cx="101.3" cy="96.8" r="79.6" />
</svg>
Embedding CSS in an SVG document lets us reuse those styles across several elements in the same document, but it prevents us from sharing that CSS between documents. That is fine for logos and icons. If you are building something like a library of chart styles, though, an external CSS file is the better choice.
With a plain text editor, you can also add CSS to SVG images created with software such as Sketch, Inkscape or Illustrator. Doing so will not affect your ability to edit the image in the drawing application, but if you edit the file with an image application, it may rewrite or strip your CSS.
Linking an SVG document to an external CSS file
As with HTML, linking to an external CSS file lets you share styles between several SVG documents. To link an external CSS file, add <? xml-stylesheet ?> at the top of your SVG file:
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="https://www.gekkode.com/en/style.css" type="text/css"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0
➥ 200 200" enable-background="new 0 0 200 200">
<circle cx="101.3" cy="96.8" r="79.6" />
</svg>
Using the <link> element
You can also use the HTML <link> element. With this method, you have to include the xmlns attribute, as shown below:
<link href="style.css" type="text/css" rel="stylesheet"
➥ xmlns="http://www.w3.org/1999/xhtml" />
Note: some older browsers need the <link> element to be wrapped in <defs> or <g> tags.
<link> is not an SVG element. It belongs to HTML and XHTML. XHTML is a variant of HTML that is parsed by the rules of XML markup. Under those rules, we can borrow elements and their behaviour from other XML dialects, such as XHTML. To do that, though, we have to tell the browser which namespace the element belongs to, using the xmlns attribute.
Using @import
We can also link to an external style sheet with @import, inside <style> and </style> tags:
<style type="text/css">
@import("https://www.gekkode.com/style.css");
</style>
This works much like the <link> method.
SVG and the <img> element: the limits
Linking an SVG file to external resources, CSS files included, does not work with the <img> element. This is a security restriction of the <img> element, built into browsers.
If you want to use linked CSS with your SVG images, you have to do one of two things:
- use a
<style>element in your SVG document to inline your CSS, - use an
<iframe>or an<object>element (see the note below).
In general, prefer <iframe> over <object>. The <object> element can be the child of an <a> element, however, whereas <iframe> cannot. Using <iframe> or <object> also makes the SVG document tree available to the parent document tree. That means we can use JavaScript to interact with it (with document.querySelector('iframe').contentDocument, for example).
Inline SVG and external assets
When you add SVG to HTML, the browser will not load the external assets referenced by the SVG document. We can, however, link to the CSS for our SVG from the <head> of our HTML document:
<head>
⋮
<link href="svg.css" type="text/css" rel="stylesheet" />
</head>
SVG elements in HTML documents are also part of the HTML document tree. With inline SVG, it is perfectly fine to combine the HTML and SVG style rules in a single style sheet.
Differences between SVG and HTML
Although SVG and HTML are both markup languages, two significant differences between them affect the way they work with CSS:
- SVG does not follow the CSS box model.
- SVG has no positioning scheme.
SVG does not follow the CSS box model
With HTML, CSS layout follows the rules of the CSS box model. SVG, on the other hand, uses coordinates for layout. It follows what is best understood as a “shape model”.
SVG shapes are not limited to rectangular boxes. As a result, most box-model properties do not apply to SVG elements. You cannot, for example, change the padding or the margin of an SVG element. Nor can you use the box-sizing, box-shadow, outline or border-* properties. Grid layout, floats and Flexbox do not work either.
You can, however, use CSS to set or change a range of SVG properties and attribute values. The full list is in the SVG 2 specification, although browser support is incomplete in most engines. Some CSS properties, such as filter, work with both SVG and HTML. We will cover a few of them in this chapter, as part of specific techniques.
SVG has no positioning scheme
When CSS is used with HTML, element boxes can:
- sit in the normal flow.
- be taken out of the normal flow with the
floatproperty, - be taken out of the normal flow with the
positionproperty.
The CSS specification calls these positioning schemes. Positioning schemes do not exist in SVG. The position property has no effect on SVG elements. Neither do properties such as top, left and bottom, which depend on element positioning. You cannot float elements in an SVG document either.
Instead, SVG uses a coordinate system to place elements. To draw a <circle>, for example, you set the coordinates of its centre point with the cx and cy attributes, then set a radius length with the r attribute. A polygon is a series of point coordinates with line segments drawn between them. In other words, you can decide where an element is drawn on the SVG canvas, but you cannot “position” it in the CSS sense of the word.
Related to positioning schemes, SVG also has no notion of z-index or stacking contexts.
Note: the SVG 2 specification defines behaviour for z-index and stacking contexts in SVG documents, but most browsers do not support it yet.
SVG elements are stacked according to their source order. Those that appear later in the document sit higher in the stack. To change the stacking order of SVG elements, you have to move them in the source or use JavaScript to reorder them in the DOM tree.
In practice, most CSS 2.1 properties do not apply to SVG documents. The exceptions are animations and transforms, display, overflow, visibility, filter and a handful of font- and text-related properties. Instead, you will have to use the SVG-specific style properties for SVG documents. Most of them can also be expressed as attributes on SVG elements.
Styling SVG elements
Here is a simple example of how to style SVG elements with CSS. Let’s start with our SVG document, a standalone file:
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="https://www.gekkode.com/en/styles.css" type="text/css" ?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink=
➥"http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 497
➥ 184" enable-background="new 0 0 497 184" xml:space="preserve">
<polygon id="star" points="77,23.7 98.2,66.6 145.5,66.5 111.2,
➥106.9,119.3,154 77,131.8 34.7,154 42.8,106.9 8.5,67.5 55.8,
➥66.6 "/>
<circle id="circle" cx="245" cy="88.9" r="67.5"/>
</svg>
This markup produces the image below.

Although we cannot use most CSS properties with SVG documents, we can use CSS to change the colour of an element. Let’s make our star yellow:
#star {
fill: hsl( 44, 100%, 50% );
}
You will often see fill used with SVG tags, for example, <circle fill="rgb( 255, 185, 0 )" cx="3" cy="10" r="100">, but it is also a property you can use in CSS.
We can also use CSS to adjust the stroke of an element, which is the outline of an SVG shape. The outline of a shape exists even when no stroke is set. Let’s give our circle a ten-pixel-wide dashed dark blue border. We will also set its fill property to cornflowerblue:
circle {
fill: cornflowerblue;
stroke: darkblue;
stroke-width: 10;
stroke-dasharray: 10, 15;
stroke-linecap: round;
}
All together, that gives us the result below.

Using SVG attributes as CSS properties
We can also use CSS to set the coordinate values of some shape elements: <rect>, <circle> and <ellipse>. Typically, we would use SVG attributes for these elements:
<svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg">
<rect x="20" y="200" width="300" height="300" fill="#f60" />
</svg>
SVG 2, however, redefined some SVG attributes as geometry properties. That means we can use CSS to set their values:
<svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg">
<style type="text/css">
rect {
x: 20px;
y: 50px;
width: 300px;
height: 300px;
fill: #f60;
}
</style>
<rect />
</svg>
Coordinate properties (x and y), centre coordinate properties (cx and cy) and radius properties (rx, ry and r) can all be set in CSS. So can width and height. Units are optional for SVG attributes. CSS values, on the other hand, require units. Lengths and percentages are both valid for the properties mentioned here, but be aware that lengths work a little differently in SVG documents. Remember that the S in SVG stands for scalable. The computed size of an SVG element also depends on:
- the computed
widthandheightof the root SVG element - the value of the
viewBoxattribute on the root element - any scaling transform applied to the element or to its ancestors.
In other words, the corners of our <rect> element are at (20, 50), (20, 320), (350, 320) and (20, 350) in the SVG coordinate system. The actual dimensions, however, may be larger or smaller depending on the factors above.
Not every SVG attribute is available through CSS, at least not in every browser. Chrome and Edge, for example, support the CSS path() function to set path data, through the d property:
path {
d: path("M 454.45223,559.21474 -304.96705,163.45948 417.4767,-296.33928 Z");
}
As things stand, they are the only browsers that do. Work to add support in Firefox and WebKit has not started yet.
For the other shape elements, the SVG 2 specification is downright inconsistent. As things stand, you have to use element attributes to set the properties of the <line>, <polyline> and <polygon> elements.
That said, we are not limited to type (or element) selectors to set these properties. We could, for example, define small, medium and large circles with class selectors:
<svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg">
<style type="text/css">
.small {
cx: 20px;
cy: 20px;
r: 20px;
fill: #0c0;
}
.medium {
cx: 80px;
cy: 80px;
r: 60px;
fill: #fc0;
}
.large {
cx: 220px;
cy: 220px;
r: 120px;
fill: #00f;
}
</style>
<circle class="small" />
<circle class="medium" />
<circle class="large" />
</svg>
Whatever the selector, using CSS syntax to set these properties also makes them easy to animate. We will see how in the next section.
Animating and transitioning SVG CSS properties
Using CSS with SVG gets more interesting when we add transitions and animations to the mix. The process is the same as animating HTML elements with CSS, but with SVG-specific properties. Let’s build a twinkling star effect with the SVG document below:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px"
➥ y="0px" viewBox="0 0 497 184" xml:space="preserve">
<defs>
<link href="twinkle.css" type="text/css" rel="stylesheet"
➥ xmlns="http://www.w3.org/1999/xhtml"/>
</defs>
<polygon class="star" points="77,23.7 98.2,66.6 145.5,66.5 111.2
➥,106.9 119.3,154 77,131.8 34.7,154 42.8,106.9 8.5,67.5
➥ 55.8,66.6 "/>
<polygon class="star twinkle" points="77,23.7 98.2,66.6 145.5,
➥66.5 111.2,106.9 119.3,154 77,131.8 34.7,154 42.8,106.9
➥ 8.5,67.5 55.8,66.6 "/>
</svg>
Our document contains two star-shaped polygon elements, each with a class name of star. To create the twinkling effect, we will animate the first one. Here is our CSS:
@keyframes twinkle {
from {
fill-opacity: .4;
}
to {
fill-opacity: 0;
transform: scale( 2 );
}
}
.star {
fill: rgb( 255,195,0 );
transform-origin: 50% 50%;
}
.twinkle {
animation: twinkle 1.5s infinite forwards ease-in;
}
Here we have used the SVG-specific property fill-opacity. As with CSS in general, if we can interpolate the value of an SVG style property, we can animate or transition it. You can see two different points of the animation in the image below.

Let’s look at another example. This time we will create a drawing effect by transitioning the stroke-dasharray property. Here is our SVG document:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
➥xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 200 200" enable-background="new 0 0 200 200">
<circle fill="transparent" stroke-width="16" cx="101.3"
➥ cy="96.8" r="79.6"/>
</svg>
stroke-dasharray accepts a comma-separated list of length or percentage values that create a dash pattern. Odd values set the length of the dash. Even values set the length of the gap. A stroke-dasharray value of 5, 10 means the stroke will be 5px long with a 10px gap between each dash. A value of 5, 5, 10 alternates 5px and 10px dash lengths with 5px gaps in between.
We can use stroke-dasharray to create a drawing effect by starting with a dash length of zero and a large gap, then ending with a long dash and no gap at all. We then transition between the two. Here is what our CSS looks like:
circle {
transition: stroke-dasharray 1s ease-in;
fill: transparent;
stroke-dasharray: 0, 500;
}
.animate {
stroke-dasharray: 500, 0;
}
At the start of the transition, our stroke is invisible because the dash length is 0 and our gap is 500. But when we add the animate class to our circle, we move the dash length to 500 and remove the gap. The effect is a bit like drawing a circle with a pair of compasses. Why 500? It is the smallest value that produced this particular effect.
An animated path future
Remember our example of setting a path through CSS in the previous section? One day, we may be able to animate paths in every browser, using CSS:
path {
d: path("M357.5 451L506.889 192.25H208.111L357.5 451Z");
transition: d 1s ease-in-out;
}
.straighten {
d: path("M357.5 8871L406 -10113.75H208.111L357.5 351Z");
}
As things stand, though, only Chromium-based browsers such as Google Chrome and Microsoft Edge support animating path definitions this way. To make it work in other browsers, use a JavaScript library such as GreenSock and its MorphSVGPlugin. Beyond browser compatibility, GreenSock and the MorphSVGPlugin make it easy to morph between two shapes, whatever the number of points in each.
Using SVG with media queries
With HTML documents, we can show, hide or rearrange parts of a page depending on viewport conditions. If the browser window is 480 pixels wide, for example, we can turn our navigation from a horizontal list into a vertical, collapsible one. We can do something similar with media queries and SVG documents. Take a logo, such as the one for the fictional company Hexagon Web Design & Development shown below.

Without media queries, this SVG logo would simply stretch or shrink to fit the viewport or its container. With media queries, we can do smarter things.
Let’s draw a distinction between the viewport of the HTML document and the viewport of the SVG document. When SVG is inline, the HTML viewport and the SVG viewport are one and the same. The SVG document behaves like any other HTML element. When an SVG document is linked, on the other hand, as it is with the <iframe>, <object> or <img> element, we are dealing with the viewport of the SVG document.
Media queries work in both cases, but when the SVG document is linked, its viewport is independent of its HTML document. In that case, the size of the browser window does not determine the size of the SVG document viewport. Instead, the viewport size comes from the dimensions of the <object>, <iframe> or <img> element. Take the following (abridged) SVG document:
<svg version="1.1" id="HexagonLogo" xmlns="http://www.w3.org/2000/
➥svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
➥ viewBox="0 0 555 174" xml:space="preserve">
<defs>
<style type="text/css">
/* CSS goes here */
</style>
</defs>
<g id="hex">
<polygon id="hexagonbg" points="55.2,162 10,86.5 55.2,11
➥ 145.5,11 190.7,86.5 145.5,162 "/>
<path id="letterH" fill="#FFFFFF" d="M58,35.5h33v35.2h18.
➥4V35.5 h33.2v103.4h-33.2v-38.3H91v38.3H58V35.5z M77.5,126.5V87.
➥3h45.6v39.2h4V47.9h-4v35.6H77.5V47.9h-4v78.6H77.5z"/>
</g>
<g id="word-mark">
<g id="hexagon-word">
...
</g>
<g id="web-design-and-dev">
...
</g>
</g>
</svg>
Note: a full demo of this technique, including the complete source of this SVG document, is available online.
On smaller viewports, let’s show only the H in a hexagon symbol:
@media (max-width: 320px) {
[id=word-mark] {
display: none;
}
}
Now, whenever the container of our SVG is 20em or less, only the symbol part of our logo will be visible.

To trigger this view from the HTML document, set the width of the SVG container:
<iframe src="hexlogo.svg" style="width: 320px; border:0"></iframe>
As you may have noticed in the image above, our SVG image keeps its intrinsic dimensions even though part of it is hidden. That, unfortunately, is a limitation of SVG. To work around it, we have to change the viewBox of the SVG document, but only when the viewport is below a certain size. This is a great use case for matchMedia.
The viewBox, as its name suggests, determines the visible area of an SVG element. By adjusting it, we can decide which part of an SVG image fills the viewport. Here is an example that uses matchMedia and a media query to update the viewBox attribute:
<script type="text/javascript">
const svg = document.querySelector( 'svg' );
/* Store the original value in a variable */
const originalViewBox = svg.getAttribute( 'viewBox' );
/* Define our media query and media query object */
const mq = matchMedia( '( max-width: 320px )' );
/* Define the handler */
const updateViewBox = () {
if (mq.matches) {
/* Change the viewBox dimensions to show the hexagon */
svg.setAttribute( 'viewBox', '0 0 200 174' );
} else {
svg.setAttribute( 'viewBox', originalViewBox );
}
}
svg.addEventListener( 'SVGLoad', updateViewBox );
/* Fire if the media condition changes */
mq.addEventListener( 'change', updateViewBox );
</script>
Now, when the SVG container is 320 pixels wide or less, the value of the viewBox attribute will be "0 0 200 174". Above 320 pixels, viewBox goes back to its initial value.

Since this technique relies on either the onload attribute or the SVGLoad event, it is a good idea to embed our CSS and JavaScript in the SVG file. When the CSS is external, SVGLoad can fire before the style sheet attached to it has finished loading.
Using media queries with background-size
SVG documents and media queries are not limited to foreground images. We can also resize the SVG viewport with the CSS background-size property.
Let’s start with this SVG document:
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
➥ xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
➥ viewBox="-20 -20 250 250" xml:space="preserve">
<style type="text/css">
circle {
stroke: #000;
stroke-width: 30;
fill: #009688;
}
@media ( width: 100px ) {
circle {
fill: #673ab7;
}
}
@media ( width: 300px ) {
circle {
fill: #ffc107;
}
}
</style>
</defs>
<circle cx="100" cy="100" r="100" />
<circle cx="100" cy="100" r="50" />
</svg>
This is a simple case. Our <circle> elements are given a new fill at specific viewport widths. By default the fill is teal, at 100 pixels wide it turns purple, and at 300 pixels, yellow.
For this to work, we have to use our SVG image as a background image and set the background-size of the selector. Here, we will use our image as the background for the <body> and for the <li> elements:
body, li {
background: url(circles.svg);
}
body {
background-color: #9c27b0;
background-size: 300px auto;
}
li {
background-position: left center;
background-repeat: no-repeat;
background-size: 1em auto;
padding-left: 40px;
font-size: 24px;
margin: 1rem 0;
}
The result is shown below.

Conclusion
Using SVG with CSS gives us more room for flexible, responsive documents. You should now know how to:
- use CSS to style SVG elements
- animate SVG properties
- use CSS media queries and the
matchMediaAPI to show and hide parts of an SVG document.


