Dec 23

Responsive SVGs

Stretch your knowledge of working with SVGs for scalable, responsive experiences.

By Nils Binder

SVG can be a tricky subject. If you're a developer, you might need to have an interest in design to really dive into SVGs. And if you're a designer, you'll need to know a bit about coding to do more than just export files from design tools like Figma. This might be why we rarely see custom-made responsive SVGs, even though they are incredibly useful and versatile. In this post, I’ll guide you through the basics of creating a responsive SVG.

I was reminded of the potential of responsive SVGs when I looked at the Threads App. In the app, there's an interesting design element where a little swirl line connects the avatar of a reply to the original message's avatar.

A post from 'zuck' from the Threads app showing a reply with a 'swirl line' connecting the avatar of the reply to the original post.

This design is achieved using three separate SVGs, with the length of the longest part calculated by JavaScript that is triggered on page load and every time the window is resized. While this is an effective approach, it relies heavily on JavaScript. In contrast, I want to show you how you can achieve a similar result using a responsive SVG and no JS at all.

To build the desired image, there are three basic things we need to understand: the viewBox attribute, the preserveAspectRatio attribute, and SVG symbols.

ViewBox Basics

<svg viewBox="0 0 300 400" xmlns="http://www.w3.org/2000/svg">
...
</svg>

Think of the viewBox in SVGs like an artboard in a design tool. For example, when you see viewBox="0 0 300 400", it's similar to saying, "Create an artboard that is 300 units wide and 400 units tall."

Remember, we're working with vector graphics, so it's best not to think in terms of pixels, as these graphics can be scaled to any size.

SVGs also have width and height attributes, just like any other image. However, there's a key difference: by default, SVGs behave as if object-fit: contain is applied. This means if the aspect ratio set by width and height is different from the one defined by viewBox, your "artboard" will always remain fully visible and maintain its proportions without distortion.

A diagram where an outer box with dimensions 600 wide by 400 high encompasses a viewBox area with the value '0 0 300 400' where the viewBox is centered in the outer box.

To add a bit more complexity, you can actually choose not to define a viewBox at all. When this happens, the actual width and height of the SVG itself become the dimensions of our "artboard." This might sound a bit confusing now, but don't worry; we'll explore this in more detail later on.

PreserveAspectRatio ↔ Object-fit

<svg viewBox="0 0 300 400" preserveAspectRatio="xMaxYMin" xmlns="http://www.w3.org/2000/svg">
...
</svg>

As I mentioned earlier, SVGs naturally act like they have object-fit: contain. There's also an attribute that functions similarly to object-position: the preserveAspectRatio property.

By default, it's set to preserveAspectRatio="xMidYMid", which centers the artboard. But you can change this setting to mimic other object-fit behaviors. For example, to replicate 'object-fit: right top', you would use preserveAspectRatio="xMaxYMin". Remember, only one of these positioning rules will be applied due to the 'contain' behavior.

A diagram where an outer box with dimensions 600 wide by 400 high encompasses a viewBox area with the value '0 0 300 400' and is aligned to the right half due to the application of preserveAspectRatio='xMaxYMin'

SVG Symbols

When we think of the viewBox in SVGs as an artboard, SVG symbols are quite similar to components in design tools like Figma. Essentially, a symbol is like a small artboard within itself, which can be inserted (multiple times) within another SVG. Each symbol comes with its own viewBox and preserveAspectRatio attributes, defining the space and boundaries of this mini artboard.

To use an SVG symbol, you first define it with a <symbol> element, giving it a unique id. This symbol acts as a template. You can then place this template anywhere in your SVG with the <use> element, which references the symbol's id.

<svg viewBox="0 0 300 400" xmlns="http://www.w3.org/2000/svg">
<defs>
<symbol id="symbol" width="20" height="30" viewBox="0 0 20 30" >
...
</symbol>
</defs>
<!-- `href` equivalent to the `id` on the symbol -->
<use href="#symbol" x="0" y="0" width="20" height="30" />
</svg>

Building a Responsive SVG

Before we put together our final SVG, let's start with the swirl element, which we'll later use as a symbol.

I began by creating an artboard with a width of 21 and a height of 25, where I drew the swirl as a path. I set the path width to 2 and chose to have round start and end points for a smooth appearance. To ensure symmetry, I positioned the start and end points of the curve such that their x-coordinates are both at 16.

An illustration of the 'swirl' using the dimensions and settings described in the previous paragraph. The loop of the swirl appears on the left side, with the two ends of the straight segments positioned in vertical alignment.

The SVG code you get after exporting your design can vary depending on the graphic tool you use. Here's what the code looked like when I exported my swirl design from Figma:

<svg width="21" height="25" viewBox="0 0 21 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_34_935)">
<path d="M16 21.5C16 19.25 16 18.6 16 15C16 7 11.75 3 7.5 3C3.2503 3 2 6.5 2 8C2 9.5 3.2503 13 7.5 13C10.5 13 16 12 16 2V0" stroke="#FF0099" stroke-width="2" stroke-linecap="round"/>
</g>
<defs>
<clipPath id="clip0_34_935">
<rect width="21" height="25" fill="white"/>
</clipPath>
</defs>
</svg>

There’s a lot of unnecessary stuff in there. Ultimately, we only need the path that defines the swirl.

I frequently rely on SVG OMG by Jake Archibald to optimize my SVGs. However, Figma often includes a clipPath, which, for our little demo at least, is not needed. Unfortunately, SVG OMG doesn't remove it, so in the end, I manually removed everything except the path tag.

<svg width="21" height="25" viewBox="0 0 21 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="..." stroke="#FF0099" stroke-width="2" stroke-linecap="round"/>
</svg>
<svg width="21" height="25" viewBox="0 0 21 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 21.5C16 19.25 16 18.6 16 15C16 7 11.75 3 7.5 3C3.2503 3 2 6.5 2 8C2 9.5 3.2503 13 7.5 13C10.5 13 16 12 16 2V0" stroke="#FF0099" stroke-width="2" stroke-linecap="round"/>
</svg>

With our swirl asset prepared, we're ready to construct the actual SVG. Let's set a width of 21 (the same width that is used for the swirl) and initially a height of 80 for this SVG. As we discussed earlier, we'll omit the viewBox attribute.

In our SVG, we'll define the swirl as a symbol within a <defs> tag. To create this symbol, we can use most of the code from the swirl SVG we created.

The main changes involve replacing the <svg> tag with a <symbol> tag and removing the xmlns attribute, as it's not necessary for symbols. What we do need is an id so it can be referenced within a use tag.

Moving it into <defs> as a <symbol> will prevent the path from being visible in the content.

<svg width="21" height="80" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<symbol id="swirl" width="21" height="25" viewBox="0 0 21 25" fill="none">
<path d="..." stroke="#FF0099" stroke-width="2" stroke-linecap="round"/>
</symbol>
</defs>
</svg>

To make the swirl path visible, we must insert a <use> tag that references this symbol. This is where the preserveAspectRatio attribute comes into play. We want our swirl to always align at the bottom of the SVG, so we'll apply preserveAspectRatio="xMidYMax" to the symbol. This ensures that the swirl is anchored to the middle bottom of the SVG.

Additionally, by setting the symbol's height to 100% via the reference within <use>, we ensure that it always matches the height of the SVG, adapting fluidly to any changes in the SVG's size.

<svg width="21" height="80" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<symbol id="swirl-1" width="21" height="25" viewBox="0 0 21 25" preserveAspectRatio="xMidYMax" fill="none">
<path d="M16 21.5C16 19.25 16 18.6 16 15C16 7 11.75 3 7.5 3C3.2503 3 2 6.5 2 8C2 9.5 3.2503 13 7.5 13C10.5 13 16 12 16 2V0" stroke="#FF0099" stroke-width="2" stroke-linecap="round"/>
</symbol>
</defs>
<use href="#swirl-1" x="0" y="0" width="21" height="100%" />
</svg>

Now we have an SVG where the swirl is always positioned at the bottom, regardless of any height changes made through CSS.

Next, let's add the long vertical line, which in the Threads App is the part with a dynamically calculated height. To do this, we'll insert a rectangle that extends from the top to the bottom of the SVG. This is easily achieved by setting the rectangle's height to 100%.

<svg width="21" height="80" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<symbol id="swirl-2" width="21" height="25" viewBox="0 0 21 25" preserveAspectRatio="xMidYMax" fill="none">
<path d="M16 21.5C16 19.25 16 18.6 16 15C16 7 11.75 3 7.5 3C3.2503 3 2 6.5 2 8C2 9.5 3.2503 13 7.5 13C10.5 13 16 12 16 2V0" stroke="#FF0099" stroke-width="2" stroke-linecap="round"/>
</symbol>
</defs>
<use href="#swirl-2" x="0" y="0" width="21" height="100%" />
<rect x="15" y="0" width="2" height="100%" rx="1" fill="#f09" />
</svg>

However, we don't actually want our rectangle to extend the full 100% height of the SVG. Ideally, it should stop just where the swirl begins.

In SVG, you cannot define coordinates starting from the bottom, but we can cleverly use CSS to achieve the desired effect. By setting the rectangle's height to 100% - 24px, we ensure that it reaches just above the swirl. This adjustment can be made directly within the SVG's code.

<svg width="21" height="80" fill="none" xmlns="http://www.w3.org/2000/svg">
<style>
#swirl-4-rect {
height: calc(100% - 24px);
}
</style>
<defs>
<symbol id="swirl-4" width="21" height="25" viewBox="0 0 21 25" preserveAspectRatio="xMidYMax" fill="none">
<path d="M16 21.5C16 19.25 16 18.6 16 15C16 7 11.75 3 7.5 3C3.2503 3 2 6.5 2 8C2 9.5 3.2503 13 7.5 13C10.5 13 16 12 16 2V0" stroke="#FF0099" stroke-width="2" stroke-linecap="round"/>
</symbol>
</defs>
<use href="#swirl-4" x="0" y="0" width="21" height="100%" />
<rect id="swirl-4-rect" x="15" y="0" width="2" height="100%" rx="1" fill="#f09" />
</svg>

And there you have it! We've successfully created a responsive SVG that adjusts its size while always keeping the swirl loop at the bottom, ensuring it doesn't get distorted.

Using responsive SVGs

Now, you might wonder, how do we actually use the swirl-line in our code? You have a few options.

Inline SVG

One of the simplest methods is to use it as an inline SVG. This approach allows you to define the symbol just once at the beginning of your document, rather than repeating it multiple times. Additionally, the style definition for the SVG can be included in your regular CSS stylesheet. It's probably best to apply these styles using a specific class, to avoid affecting all rectangle elements globally.

Using an image tag

You can also save the code as an SVG file and then use an image tag. Please note that for this approach, you have to delete the width and height attributes from the SVG and instead apply them to the <img> Tag.

CSS in SVG in CSS 🤯

Perhaps the most thrilling option available is embedding the SVG directly in our CSS as a data URI background-image.

For this, I recommend a fantastic tool by @yoksel, which you can find at https://yoksel.github.io/url-encoder/. This tool allows you to simply copy and paste any SVG and generates a ready-to-use code snippet for your stylesheet. You can then, for example, use a pseudo-element and apply the background image.

This process essentially embeds CSS within an SVG, which is then itself embedded within your CSS file. Isn't that mind-blowing?

Putting it all together

Check out this CodePen, where you'll find all three examples demonstrated. While I don't have a strong preference for any particular solution, I'm somewhat partial to the last option. The reason is, that it allows for the creation of the swirl without the need for an additional DOM element, which I find quite efficient and elegant.

See the CodePen.

Nils Binder

Nils Binder

Nils moves between two worlds — continuously striving to improve the communication between designers and developers. Starting as a so-called web-designer in 1999, he now calls himself "frontend designer." He worked for a wide variety of clients, from startups to global players. Also, he's an origami enthusiast spending hours folding paper.

Nils selected Queermentor for an honorary donation of $50

Queermentor

Queermentor is committed to promoting equity by providing free mentoring, training, and networking opportunities for queer individuals. They also serve as a resource for companies looking to make a positive impact on the LGBTQIA+ community through their work.