Morphing icon states with d: path()
Drawing with SVG's <path> syntax is approachable once you limit yourself to a viewBox="0 0 100 100" and stick to commands you actually understand. The straight-line commands such as L are easy, and the curved Q command is fairly intuitive. That is enough to build shapes that also animate in CSS, at least in Chromium browsers.
<svg viewBox="0 0 10 10">
<path d="M2,2 L8,8" />
</svg>
svg:hover path {
transition: 0.2s;
d: path("M8,2 L2,8");
}
A stateful icon
A common need is a UI element whose icon differs by state. For a "log"-style shape, the default is four straight lines — closer to lines of text than a hamburger — with additional states for ACTIVE, SUCCESS and ERROR.
- DEFAULT
- ACTIVE
- SUCCESS
- ERROR
Driving that with markup was a matter of writing what amounts to the most complicated state machine in the world.
const indicator = document.querySelector(".element");
let currentState = indicator.dataset.state;
indicator.addEventListener("click", () => {
let nextState = "";
if (currentState == "DEFAULT") {
nextState = "ACTIVE";
} else if (currentState == "ACTIVE") {
nextState = "SUCCESS";
} else if (currentState == "SUCCESS") {
nextState = "ERROR";
} else {
nextState = "DEFAULT";
}
indicator.dataset.state = nextState;
currentState = nextState;
});
That markup makes it possible to style each state through data-attributes.
.element {
&[data-state="DEFAULT"] {
}
&[data-state="ACTIVE"] {
}
&[data-state="SUCCESS"] {
}
&[data-state="ERROR"] {
}
}
The element can start in the default state of four lines.
<div class="element">
<svg viewBox="0 0 100 100" class="icon">
<path d="M0, 20 Q50, 20 100, 20"></path>
<path d="M0, 40 Q50, 40 100, 40"></path>
<path d="M0, 60 Q50, 60 100, 60"></path>
<path d="M0, 80 Q50, 80 100, 80"></path>
</svg>
</div>
From there CSS can replace the paths for the remaining states.
Matching point counts is the constraint
Only paths with the same number and type of points can be animated in CSS. The four "straight" lines in this example each contain an unused curve point, and that extra point is what opens the door to morphing them. Substitute the new paths and you get something close to a circle.
.editor-indicator {
&[data-state="ACTIVE"] {
.icon {
:nth-child(1) {
d: path("M50, 0 Q95, 5 100,50");
}
:nth-child(2) {
d: path("M100, 50 Q95, 95 50,100");
}
:nth-child(3) {
d: path("M50,100 Q5, 95 0, 50");
}
:nth-child(4) {
d: path("M0, 50 Q5, 5 50, 0");
}
}
}
}
The other states use a crude checkmark for SUCCESS and a crude exclamation point for FAILURE. The demo, again limited to Chromium, changes states on click.
Why this often gets dropped
Neither Firefox nor Safari supports d: path(); in CSS. It is not that those browsers fail to animate the property — the property simply does not work there, which ruled the approach out for this use case; icons were swapped per state instead. Shape morphing that must work across browsers is a separate problem with its own tooling.



