CSS and SVG, Large and Small

We're naturally interested in SVG because it gives us an easy way to draw live vectors in the browser—shapes that can be manipulated, listen for events, and announce themselves in a screen reader. But SVG is also interesting because its presentation can be manipulated using CSS, including media queries and complex selectors. This opens up new possibilities, both for data visualization and art direction.

To really understand how SVG interacts with CSS, we need to talk about how it gets included in the page. Thus far, we've been generating or working with inline SVG, which is content directly placed in an HTML page. The HTML5 parser guarantees that inline <svg> tags simply work, although we do need to use the correct namespace when generating new elements dynamically. So this just works:

<div>
  <svg width="100" height="100">
    <rect x="10" y="10" height="40" width="40"></rect>
  </svg>
</div>

You can also include a separate SVG file in a page, same as a JPG or PNG file, using an <img> tag.

<img src="filename.svg">

However, when included through an image tag, the SVG is "opaque" to the rest of the page: it can't be inspected, you can't access its internal elements from scripts or styles, and it's not allowed to load external resources or run scripts. This makes them largely inert compared to inline elements, but they can still include internal CSS, and they can expose some manipulation capability through the URL used to load them.

The media is the message

When using media queries with SVG, the difference becomes clear. For example, here's the same SVG loaded twice: once through an inline tag, and once as an external resource. In both cases, the SVG contains a <style> tag that sets the fill of the rectangle with a 400px breakpoint:

<svg width="100" height="100" viewBox="0 0 100 100">
  <style>
rect { fill: blue }
@media (min-width: 400px) {
  rect { fill: green }
}
  </style>
  <rect x="20" y="20" width="60" height="60"></rect>
</svg>

If you're on a desktop machine, try resizing your window to see when the square changes color.

You should see the second square, the image tag, change color slightly before the first. This is because for the image, the "viewport" that determines the effect of the media query isn't the browser window, it's the image tag itself. The behavior is similar to that of an iframe, in that the "window" for the enclosed contents is basically just the tag.

This ability to adjust styles based on the size of the image element, and not the size of the browser window, makes external SVG ideal for loading resources like icons or logos. When the element is styled smaller, you can use the stylesheet to hide details for readability. When it's shown as a larger part of the page, the same file can add detail, incorporate extra text, or even swap out parts of the image entirely.

Powering small multiples with CSS

Loading external SVG files can be helpful for static elements. But generally speaking, the most effective way to include an SVG into your page is still as inline elements. That doesn't mean you have to write them all in directly—I'm a big fan of using templating or a custom element like our Savage Image tag, which loads the SVG in a separate network request and then places it in the document for you. This gives you the best of both worlds: small page size and load time, combined with the ability to access and style elements directly.

Let's take a look at one potential method for using inline SVG in data visualization. We'll create a graphic that uses a template and CSS classes to generate small multiples. In this case, the graphic represents injuries to a football team as a set of paper dolls, with injured limbs shaded. Here's our template function, which takes a string of injuries and then adds those to the data-injuries attribute of its output.

var generatePlayer = function(injuries) {
return `
<svg width="100" height="140" data-injuries="${injuries}">
  <g class="head">
    <circle cx="50" cy="20" r="20" />
  </g>
  <g class="torso">
    <rect x="25" y="40" width="50" height="60" />
  </g>
  <g class="right-arm">
    <rect x="0" y="40" width="25" height="20" />
  </g>
  <g class="left-arm">
    <rect x="75" y="40" width="25" height="20" />
  </g>
  <g class="right-leg">
    <rect x="25" y="100" width="15" height="40" />
    <rect x="10" y="120" width="25" height="20" />
  </g>
  <g class="right-leg">
    <rect x="60" y="100" width="15" height="40" />
    <rect x="60" y="120" width="25" height="20" />
  </g>
</svg>
  `;
};

A little CSS, meanwhile, will cause set the fill of shapes inside groups that match each injury string (see the chapter on attribute selectors for more detail on how this works):

[data-injuries~="head"] .head * { fill: red }
[data-injuries~="torso"] .torso * { fill: red }
[data-injuries~="left-arm"] .left-arm * { fill: red }
[data-injuries~="right-arm"] .right-arm * { fill: red }
[data-injuries~="left-leg"] .left-leg * { fill: red }
[data-injuries~="right-leg"] .right-leg * { fill: red }

Putting these two together, we can generate a set of paper dolls that will quickly color themselves based on the data we simply pass through:

Pardon my artistic skills, of course: I'm no designer. However, you can see this same technique used with much more panache in the Seahawks injuries database built by my colleagues, Emily Eng and Vanessa Martínez.

As demonstrated by Emily and Vanessa's work, part of the advantage of working with CSS classes to style child nodes within an SVG is that it's well-suited to a production process that integrates graphic artists or designers: assets produced in Illustrator with IDs given to groups can then be easily used by a developer to style subsets of the image. I recommend having the graphic produced with all the highlights "turned on" with a common style, which will link them to a shared class. That class can then be turned off in CSS until the correct attribute or class is applied to its parent element.

The limits of styling SVG

At this point, I hope you're excited by the possibilities: we can use CSS to alter our images, either in response to JavaScript-driven markup or to anything exposed via media query (screen size, orientation, media type, or input type). But don't get too thrilled: although we can do a lot with styles, they're still limited in what they can accomplish.

Basically, you can use any style properties that are included in the list of presentation attributes:

alignment-baseline, baseline-shift, clip, clip-path, clip-rule, color, color-interpolation, color-interpolation-filters, color-profile, color-rendering, cursor, direction, display, dominant-baseline, enable-background, fill, fill-opacity, fill-rule, filter, flood-color, flood-opacity, font-family, font-size, font-size-adjust, font-stretch, font-style, font-variant, font-weight, glyph-orientation-horizontal, glyph-orientation-vertical, image-rendering, kerning, letter-spacing, lighting-color, marker-end, marker-mid, marker-start, mask, opacity, overflow, pointer-events, shape-rendering, stop-color, stop-opacity, stroke, stroke-dasharray, stroke-dashoffset, stroke-linecap, stroke-linejoin, stroke-miterlimit, stroke-opacity, stroke-width, text-anchor, text-decoration, text-rendering, unicode-bidi, visibility, word-spacing, writing-mode

Notably missing from this list are the important coordinate-related attributes: no x, no y, no width or height. You can fake some of those with transform, but you cannot apply transforms via CSS to SVG elements in IE or Edge.

This limits our options somewhat when it comes to creating responsive media, because we can't actually reposition elements. It's possible to create some responsive special effects using nested SVGs and creative use of preserveAspectRatio, as Sara Soueidan has written. It may also be possible to take advantage of the small file size of most SVG files by simply duplicating the layers you want to move at different positions, and showing only the one you want.