DEV Community

pop3zxcv
pop3zxcv

Posted on

Four things SVG and CSS did that I did not expect

I spent a while building an icon editor that runs entirely in the browser
(icons.jamuny.com, free, no account). Here is what cost me the most time.

A presentation attribute loses to any author CSS rule

I was scaling handle stroke widths by 1 / zoom and writing the result as an
attribute. The value was never used.

handle.setAttribute('stroke-width', String(0.35 / zoom));
Enter fullscreen mode Exit fullscreen mode

.handle { stroke-width: 0.35 } in the stylesheet outranks it, because a
presentation attribute sits at the very bottom of the cascade.

Measured in Chromium: an attribute of 0.05 computed as 0.35px. Every handle
thickened on screen as you zoomed in, for months, with no error anywhere.

The fix is a custom property, which is an ordinary declaration and wins where an
attribute cannot:

layer.style.setProperty('--px', String(1 / zoom));
/* .handle { stroke-width: calc(0.35 * var(--px)) } */
Enter fullscreen mode Exit fullscreen mode

Geometry attributes like r and width are unaffected. They have no CSS
counterpart here, so nothing was ever overriding them.

A focused SVG element gets a focus ring measured in user units

My canvas is 24 units wide and about 620 pixels. Chrome drew its default focus
ring at outline-width: 2.72727px in user units, which is about 24 screen pixels.

A fat blue disc appeared around every point you clicked. It was reported to me
four times, and four times I thinned something of my own that was not the cause.

getComputedStyle(document.activeElement).outline
Enter fullscreen mode Exit fullscreen mode

That one line found it. My rule only covered :focus-visible, which is the
keyboard case, and the keyboard case is the one where I draw a ring of my own.

var() does work in a presentation attribute, and I wrote down that it doesn't

I needed a segment colour that changes with the theme, so the value is
oklch(var(--band-l) var(--band-c) 47). I applied it through a style and put a
comment beside it saying var() is not substituted in presentation attributes.

It is. Both forms compute to the same colour, including on an element built
detached and appended afterwards:

a.setAttribute('stroke', 'oklch(var(--band-l) var(--band-c) 47)');
b.style.stroke      =    'oklch(var(--band-l) var(--band-c) 47)';
// both: "oklch(0.52 0.09 47)"
Enter fullscreen mode Exit fullscreen mode

Nothing broke, because both work. But the comment passed a full test suite and a
deploy, which is the point: a claim about the browser that nobody pointed a
browser at is worth exactly nothing.

Making an overlay translucent breaks contrast in a way tests cannot see

Each drawing command wears its own colour band over the icon. A request came in
to make those bands 74% opaque instead of solid.

A translucent colour takes on whatever is behind it, so one palette stopped
serving both themes. Over white it landed at 2.29:1 against that white, under
the 3:1 that WCAG 1.4.11 asks of a graphic.

I swept the lightness range and no single value clears the bar in both themes at
that opacity. So the lightness became a theme token: 0.52 on light, 0.74 on dark.

My contrast test stayed green through all of it, because it read the band's raw
stroke and a raw colour says nothing about opacity. It composites now:

const over = (colour, backdrop, alpha) =>
  colour.map((part, at) => alpha * part + (1 - alpha) * backdrop[at]);
Enter fullscreen mode Exit fullscreen mode

I checked the fix by forcing each theme's lightness onto both and watching the
test go red. A test you have not seen fail is not known to protect anything.

One more thing about palettes

I had a tab of coolors.co open on "soft" palettes while doing this. Pulled the
hex values out of the top twelve and measured them.

Every one lands between 5.6:1 and 6.1:1 on a dark ground and between 1.03:1 and
1.08:1 on a white one. They are dark-ground palettes, not soft ones.


The editor is at icons.jamuny.com. No account, no upload, and the CSP says
connect-src 'none' so it could not upload anything if I wanted it to.

Top comments (0)