Adaptive SVGs with symbols instead of duplicate files

A client wanted a hero illustration with the feel of a high-end cartoon: complex paths, subtle gradients, specific animations. On a desktop screen, in a 16:9 widescreen layout, it looked great. On mobile the whole thing fell apart, either shrinking into an unreadable mess or getting cropped until it lost the point. What we needed were Adaptive SVGs that responded to context without wrecking our Lighthouse scores.

My first thought was the lazy option: two separate SVG files, swapped with a CSS media query. I considered a simple WP_Query serving different templates too, but either route signs you up for a maintenance problem. Change one path in the character’s eye and you are now updating it in two places, and the browser downloads twice the assets for nothing. That road ends in a bloated DOM and a grumpy client.

Why loading two SVGs is a performance trap

Traditional responsive images, the <picture> element included, treat an SVG as a static block. Put an SVG in an <img> tag or a picture source and you lose the ability to reach inside and animate individual components with CSS. So treat the artwork as a component library instead. It is the same principle I use for custom social icons in WordPress, just at a much larger scale.

The <symbol> element solves this. You define a graphic once and reference it as many times as you like, which is DRY (Don’t Repeat Yourself) applied to vector art. Since the symbols sit in one hidden library, the browser parses the heavy path math once, no matter how many times the page uses them.

Building the adaptive SVGs symbol library

Start by extracting the elements. Rather than exporting one giant scene, export each moving part or character with its own viewBox so it gets its own coordinate system. Then wrap those in a hidden SVG near the top of the document, or in a separate file if you run an SVG sprite loader.

<!-- Hidden Library -->
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
  <symbol id="bbioon-quick-draw-hat" viewBox="0 0 294 182">
    <g class="bbioon-hat-content">
      <!-- Path data here -->
      <path d="..." />
    </g>
  </symbol>
</svg>

<!-- Mobile View (3:4) -->
<svg class="bbioon-svg-mobile" viewBox="0 0 1080 1440">
  <use href="#bbioon-quick-draw-hat" width="294" height="182" transform="translate(100, 50)" />
</svg>

<!-- Desktop View (16:9) -->
<svg class="bbioon-svg-desktop" viewBox="0 0 1920 1080">
  <use href="#bbioon-quick-draw-hat" width="294" height="182" transform="translate(500, 200)" />
</svg>

Positioning elements across breakpoints

Now you move the <use> reference instead of fighting the original drawing’s internal coordinate system. That is the part that makes Adaptive SVGs work. CSS media queries hide the desktop SVG and show the mobile one, and since both point at the same #bbioon-quick-draw-hat, the browser has no extra parsing to do.

The transitions come out smoother this way, which helps with improving user experience. For the technical specs on symbols, start with the MDN symbol documentation, which spells out how the shadow DOM behaves here.

Animating symbols that live in the shadow DOM

I lost hours trying to target a class inside a <symbol> from my main stylesheet. It never worked, because the <use> element creates a clone in the shadow DOM and a standard CSS selector cannot reach into it. What does work is an attribute substring selector, or targeting the <use> element itself and letting inherited properties like fill and stroke do the rest.

/* This targets the specific instance of the hat */
use[href="#bbioon-quick-draw-hat"] {
  animation: bbioon-hat-rock 2s infinite ease-in-out;
  transform-origin: center bottom;
}

@keyframes bbioon-hat-rock {
  0%, 100% { transform: rotate(-3deg) translate(500px, 200px); }
  50% { transform: rotate(3deg) translate(500px, 200px); }
}

For more involved movement, my guide on fixing stiff UI with natural SVG animations covers how to stop these movements feeling robotic.

What this looks like in practice

  • Stop duplicating path data. Define it once in a <symbol>.
  • Use several <svg> wrappers with different viewBox values to rearrange the scene for mobile and desktop.
  • Remember that <use> elements are shadow clones, so target them directly when you animate.
  • The CSS-Tricks guide on SVG symbols has more on sprite management.

If you are stuck in a mess of unoptimized graphics and broken layouts, I have probably seen it, and fixed it, before. Reach out if you want someone to take over the heavy lifting.

If you are still loading a different image for every screen size, a component-based SVG workflow will save you the duplication.

author avatar
Ahmad Wael
I'm a WordPress and WooCommerce developer with 15+ years of experience building custom e-commerce solutions and plugins. I specialize in PHP development, following WordPress coding standards to deliver clean, maintainable code. Currently, I'm exploring AI and e-commerce by building multi-agent systems and SaaS products that integrate technologies like Google Gemini API with WordPress platforms, approaching every project with a commitment to performance, security, and exceptional user experience.