Scaling SVGs without scaling their strokes (2025 edition)

August 11, 2025

Two years ago, I wrote about inconsistencies I encountered while scaling line art SVGs without changing stroke width. At the time, vector-effect was broken.

Inspired by Josh Comeau’s introduction to SVG, I recently revisited the issue. Today, it mostly works (with caveats). Here’s what I found.

I had a reference design that I was working with, which I will share again below.

A screenshot of the hero section  of a website. The text in the hero section is centred. Its contents are not relevant. To the left and right of the text are line-art renditions of a world map, in which you can see outlines of the east coast of North America and the west coast of Europe. The text in the hero section is floating above the ocean.

The SVGs I want to use are on either side of the hero text.

There were a few reasons this was important to me:

  • While I could simply make these SVGs the src element of an img, that increased the overall payload of the page, which I didn’t want. (Ironically, using a jpeg or a png file both save more space with this method, which is what I’ve had to resort to.)
  • If I can directly embed the SVG, I can change stroke-width and fill programatically, which comes in handy depending on the background-colour of the page, making the whole site more flexible and easier to work with.
  • Embedding the SVG directly appears sharper than using a jpeg or a png.
  • And the most important part: if I could embed the SVG, I could theoretically keep the stroke width constant while the page resized, ensuring the design element was visible at multiple resolutions.

In 2023, this was broken. Today, it appears to work, with caveats.

My code remains the same:

<style>
svg {
	display:block;
	margin:0;
	width:100%;
	height:auto;
}
</style>

<svg viewBox="0 0 500 240">
  <path
    vector-effect="non-scaling-stroke"
    d="M10,20 L40,100 L39,200 z"
    stroke="black"
    stroke-width="1px"
    fill="none"></path>
</svg>

Today, this works. It works without using transform=scale(X,Y). I don’t know why it works or what changed. (I do know that vector-effect has had a somewhat buggy history going all the way back to 2018, which is a little discouraging, but hopefully those days are behind us.)

However, the implementation is not flawless.

To verify that this is working more or less as described, I took several screenshots at different viewports of the map in action, then zoomed in to 1200% in Photoshop. Then I took screenshots in Photoshop of the results. You will see that vector-effect is not flawless, but it’s close.

In these examples, it is tough to say if the problem is the vector-effect spec, or if the problem is Chrome’s implementation of the spec.

The map is in yellow. The background is blue.

A screenshot of a line art from Photoshop. The screenshot is zoomed in to 1200%. The line art is about 1px wide.

SVG line art in Google Chrome in a viewport smaller than the SVG's viewbox.

At this viewport size, when the SVG is smaller than the viewbox element, you can see from this zoom level that the stroke barely qualifies as one pixel wide. But it’s close enough, and the stroke width is much better than a resized jpeg or png file at the same size.

A screenshot of a line art from Photoshop. The screenshot is zoomed in to 1200%. The line art is about 2px wide.

SVG line art in Google Chrome. The viewport is roughly twice the size of the viewbox.

In a larger viewport, it looks like the stroke is rendered at 2px wide. But again, this is close.

A screenshot of a line art from Photoshop. The screenshot is zoomed in to 1200%. The line art is about 4px wide.

SVG line art in Google Chrome. The viewport is roughly three and a half times the size of the viewbox.

At a very large viewport size, Chrome renders this at a much chunkier stroke width of about 4px or so. This probably a dealbreaker for some art, but for my purposes, this works. It’s worth noting this is when the image has more than tripled the original size of the viewbox. At this size, this 300px wide SVG was being rendered at over 1000px wide. 

In 2023, the situation was much more dire. vector-effect:non-scaling-stroke had the same rendering as vector-effect:none. With vector-effect:none, the stroke width appears at least three times thicker at large viewport sizes.

I do not know why Chrome still renders stroke-width:1px; vector-effect:non-scaling-stroke as a 4px stroke width at this size. Obviously, it’s a discrepancy. But without digging out Photoshop and zooming in to 1200%, I’m not sure I would have caught the error. It’s possible Chrome is accounting for some sort of optical illusion I haven’t considered in its presentation.

However, even at similarly large sizes, Safari behaves better:

A screenshot of a line art from Photoshop. The screenshot is zoomed in to 1200%. The line art is about 2px wide.

SVG line art in Safari. The viewport is roughly four times the size of the viewbox.

As you can see, this is closer to 1px (with perhaps some antialiasing).

I will acknowledge my efforts here aren’t exactly scientific. My screenshots aren’t always the same dimensions, etc. I’m doing this quickly because I have a job to do. But the differences between how Chrome and Safari render vector-effect:non-scaling-stroke are obvious to the naked eye. Without zooming in to Photoshop, it’s clear that Chrome’s implementation is flawed, and Safari’s is closer to accurate.

That being said, this is so much better than it was in 2023. In 2023, I couldn’t use this technology in production, and wouldn’t recommend it to my client. As of August 2025, I can use this. Before, the spec was entirely broken in Chrome. Now, I am describing specific differences in the implementation of this spec between browsers. This is a marked improvement.

So it’s a good day.

As of July 2025, I have space for only two new clients this year. Please don’t wait, or we will both be sad. You can email me, book a call, or fill out my project questionnaire.