For you copy-pasters out there, let’s get right to the code block:

  <svg>
  <!--[if gt IE 8]><!-->

  <switch>
    <g class="hide-me-from-older-browsers">
      (Your inline SVG goes here)
    </g>

    <foreignObject>
      <!--<![endif]-->
      (Your HTML fallback goes here)
    </foreignObject>
  </switch>
</svg>

  @namespace svg "http://www.w3.org/2000/svg";

.hide-me-from-older-browsers {
  display: none;
  visibility: hidden;
}

svg|g.hide-me-from-older-browsers {
  display: inline;
  visibility: visible;
}

That’s the entire technique. Works in IE6, Firefox 2, Safari 3, etc. Also improves on the older method:

  • Works with <foreignObject>
  • Lets you use links inside the SVG (<a xlink:href>)
  • More accessible to users stuck on older browsers
  • Just one implementation, instead of guards around every <text> and <desc> and a class on every <image>

There are 3 tricks for this snippet: one for modern browsers, one for old Internet Explorer, and one for all the other SVG-incapable browsers.

Unqualified <switch>

The first problem is we only want the HTML fallback to show in browsers that don't support inline SVG.

The key is SVG has the interesting element <switch>:

The switch element evaluates the requiredFeatures, requiredExtensions and systemLanguage attributes on its direct child elements in order, and then processes and renders the first child for which these attributes evaluate to true. All others will be bypassed and therefore not rendered.

It conditionally displays one of its child elements, depending on the viewer’s supported SVG features and language settings.

The trick is to not bother with any of the “conditional processing attributes”:

  <switch>
  <g> <!-- I get rendered! --> </g>
  <g> <!-- I don’t. --> </g>
</switch>

In browsers that understand inline SVG, they will render the first element where they meet the qualifications, and since the first element is unqualified, it always gets rendered.

So first we set up our inline SVG with an unqualified <switch> at the root, like this:

  <svg>
  <switch>
    <g> <!-- your beautiful inline SVG graphics here --> </g>
    <foreignObject> <!-- your fallback HTML here --> </foreignObject>
  </switch>
</svg>

Capable browsers won't show anything inside the fallback <foreignObject>.

Optimizing for modern browsers

However, their preload scanners, being not so smart, will still request any images and/or scripts they find in there. Considering your Scalable Vector Graphics fallback is likely an image, this might be an unacceptable performance hit. If so, consider the following regrettable hack:

  <foreignObject>
  <img src="1x1.gif" style="background-image: url(real-image-here.png)"
       width="300" height="200"
       alt="Accessibility matters, especially for older browsers!">
</foreignObject>

SVG-capable browsers won’t put the fallback <img> in the render tree and therefore won’t request its background-image, and their preloaders will only grab the tiny, ultra-cacheable 1-pixel GIF. I experimented with omitting the src entirely, or setting it to //:0, but that sort of thing has odd side effects at best, and at worst can result in broken image icons or visible alt text over the background image.

If the inline SVG is pure decoration, you could replace the stretched GIF with a <div> and prevent the (tiny) preloader request. But be sure you’re not locking anyone out who has an older browser and a disability; for a long, long time, screen-readers only worked well with Internet Explorer, so IE8 has disproportionate market share with assistive technology.

Go away IE, you break everything

For the older browsers, some SVG elements leave ghosts of themselves. The contents of <desc> and <text> show through, and <image> will crop up as a broken image icon. And since it’s old IE, we can’t style them away; remember why we needed the HTML5 shiv?.

So we wrap that stuff up in a conditional comment:

  <!--[if gt IE 8]><!-->
<svg>
  <switch>
    <g>
      (Your inline SVG goes here)
    </g>

    <foreignObject>
      <!--<![endif]-->
      (Your HTML fallback goes here)
    </foreignObject>
  </switch>
</svg>

IEs 8 and below will get just the HTML fallback, and also the closing tags of <foreignObject>, <switch>, and <svg>. You can wrap those up too if you're really paranoid, but this is what IE will do with them:

  </foreignObject><//foreignObject>
</switch><//switch>
</svg><//svg>

3 empty inline elements, which should collapse to nothing. I’d be surprised if you’d need to hide them.

.hide-me-from-older-browsers

Internet Explorers are one thing, but there are still other browsers floating around that don't deal with inline SVG. About ~4% of the world, if caniuse is to be believed. And from puttering around in CrossBrowserTesting, they rarely seem fooled by the CDATA hack.

Thankfully, they’re easier to deal with than old IE. We can style the first child of the unqualified switch to hide it:

  .hide-me-from-older-browsers {
  display: block;
  width: 0;
  height: 0;
  overflow: hidden;
}

Which is an effective way of hiding things in the HTML box model, but in SVG, none of those CSS properties accomplish anything. Voila, no inline SVG ghosts in the old machine!

Unfortunately, this is a really bum deal for assistive technology users stuck on older browsers; they’ll get both the fallback and the SVG stew that neither their UA nor they can understand. The standard method of making sure screen-readers and other assistive tech don't accidentally read out an element that nobody should see is to set both display: none and visibility: hidden on it.

Unfortunately, both of those properties do have the intended effect in SVG, so we can't use ‘em. Or can we?

CSS namespaces

Here’s an at-rule you’ve probably never used: @namespace. It’s for mixing two XML standards in the same document, and they share element names. Like how SVG and HTML both have <a>. This CSS:

  @namespace svg "http://www.w3.org/2000/svg";

…is equal to this XML:

  xmlns:svg="http://www.w3.org/2000/svg"

If you write a { display: none }, it affects both the HTML and the SVG <a> elements. But with that CSS namespace declaration, you could write:

  svg|a { display: none }

And it would only affect SVG's <a> elements. This doesn’t need XHTML to work; it functions in friendly old HTML5! Happily, supporting CSS Namespaces was a requirement for inline SVG, so it should work everywhere <svg> does. So we write:

  @namespace svg "http://www.w3.org/2000/svg";

.hide-me-from-older-browsers {
  display: none;
  visibility: hidden;
}

svg|g.hide-me-from-older-browsers {
  display: inline;
  visibility: visible;
}

And bam, it works as the W3C intended. Note that you must include the tag name after the namespace delimiter (the |), or Firefox and IE will refuse to work with it. Chrome and Safari don’t care.

Testing codepen

I tested this technique pretty thoroughly, but I'm sure there’s always edge cases. This is what I used: