Styling Inline SVGs for Light and Dark Mode with CSS Variables and Fallbacks

October 2025

Introduction

I've started using SVGssvgs to make handwritten notes I can include here on my site.

I'm using a drawing app called Conceptsapp to make them. It hard codes fill and stroke colors directly on each path. That makes total sense for a drawing app. I need something more.

Switching Modes

My site has two color modesmodes:

  • Light Mode (dark text on a light background), and
  • Dark Mode (light text on a dark background)

Writing CSS to switch between the two for HTML elements is straightforward. You write a set of defaults. Then, you make an alternate version inside a @media query.

What's great is that the same CSS approach works for SVGs. You don't even need to remove the hard coded values. In fact, you should leave them in. Apps that don't work properly with CSS variable properties in SVGs fall back to them.

Here's what the approach looks like:

CSS

page CSS
:root {
  --svg-rect-fill: green;
}

@media (prefers-color-scheme: dark) {
  :root {
    --svg-rect-fill: rebeccapurple;
  }
}

HTML

<svg id="demo-1" width="400" height="200" xmlns="http://www.w3.org/2000/svg" version="1.1">
  <defs>
    <style><![CDATA[
      #demo-1 {
        --backup-fill: orange;
      }
      @media (prefers-color-scheme: dark) {
        #demo-1 {
          --backup-fill: yellow;
        }
      }
      #demo-1 rect {
        fill: var(
          --svg-rect-fill, 
          var(--backup-fill)
        );
      }
    ]]></style>
  </defs>
  <rect fill="blue" x="60" height="180" y="10" width="130"/>
</svg>

Output

Here's the breakdown of where the colors come from:

  • green: You're in Light Mode in a browser/app that supports CSS variable properties in SVGs and they were picked up from the page level stylesheet.
  • purple: You're in Light Mode in a browser/app that supports CSS variable properties in SVGs and they were picked up from the page level stylesheet.
  • orange: You're in Light Mode in a browser/app that supports CSS variable properties in SVGs but they page level settings weren't picked up and the fallbacks from inside the SVG stylesheet were used.
  • yellow: You're in Dark Mode in a browser/app that supports CSS variable properties in SVGs but they page level settings weren't picked up and the fallbacks from inside the SVG stylesheet were used.
  • blue: You're in a browser/app that does not support CSS variable properties in SVGs so the hard coded attributes from the rect element were used directly.

Here's the same example with an intentionally busted call to the page level varialbes to demonstrate what happens when they don't work for some reason (i.e. getting orange/yellow for light/dark mode with blue still being the fallback if the variables don't work in the browser/app.)

HTML

<svg id="demo-2" width="400" height="200" xmlns="http://www.w3.org/2000/svg" version="1.1">
  <defs>
    <style><![CDATA[
      #demo-2 {
        --backup-fill: orange;
      }
      @media (prefers-color-scheme: dark) {
        #demo-2 {
          --backup-fill: yellow;
        }
      }
      #demo-2 rect {
        fill: var(
          --intentionally-missing-var, 
          var(--backup-fill)
        );
      }
    ]]></style>
  </defs>
  <rect fill="blue" x="60" height="180" y="10" width="130"/>
</svg>

Output

Wrapping Up

I haven't done much SVGs. It always felt fraught. Especially because working with them as images instead of inline SVGs doesn't offer all the same styling options. But, I added a feature to bitty that lets me grab external SVGs and move them to inline DOM elements.

CSS becomes fully operational. SVGs cross the threshold into being easy to think about.

Can't wait to play with them.

-a

end of line

Endnotes

The original version of this post removed the hard coded values from the SVG (e.g. fill and stroke). I thought they needed to be gone for the CSS to work. That's not the case. Also, without them, apps like Inkscape and the NetNewsWire RSS Reader don't show anything.

I'm using an ID on the <svg> to make sure the style doesn't interfere with other SVGs on the page. Didn't have that when I first started and learned I needed it really quickly.

Wiring up all the parts is verbose. Doing it by hand is a non-started. I'm opting to go with letting the computer do it for me.

I'm wrapping the <path> elements in my notes with a <g> tag with fill and stroke attributes on it. Those values cascade down to the <path> elements which saves space in the file size.

References

It took a little digging to confirm that CSS styles would overwrite the hard coded values in the SVG elements. This is what I'm using for confirmation that they do:

For user agents that support CSS, the presentation attributes must be translated to corresponding CSS style rules according to rules described in Precedence of non-CSS presentational hints ([CSS2], section 6.4.4), with the additional clarification that the presentation attributes are conceptually inserted into a new author style sheet which is the first in the author style sheet collection. The presentation attributes thus will participate in the CSS2 cascade as if they were replaced by corresponding CSS style rules placed at the start of the author style sheet with a specificity of zero.

In general, this means that the presentation attributes have lower priority than other CSS style rules specified in author style sheets or ‘style’ attributes.

Footnotes

A code based way to make images that scale up forever.

I'm going with the Concepts app because it offers a way to pay them once instead of signing up for a subscription. I like paying for software since that means folks can continue to work on it, but subscriptions burn me.

Light and Dark Mode

The browser automatically switches between light and dark mode if it can read user preference settings. Adding an explicit switch is in the works. So is creating high contrast color modes.