DEV Community

Cover image for Prototype hover, press and focus animation in SVG before you build the app
Usman Bashir
Usman Bashir

Posted on Originally published at svglab.app

Prototype hover, press and focus animation in SVG before you build the app

A mockup cannot tell you whether a button answers a press in 150 milliseconds or 600. You find out by using it. Building the real interaction means wiring state, routing and data first, so the cheap move is to prototype the motion on the screen itself, before the app exists.

An SVG screen is a good place to do it. It is markup, CSS works on its elements, and the whole prototype opens in a browser tab.

The three states of a button

.btn { fill: #B5FF3A; transform-box: fill-box; transform-origin: center;
       transition: fill 150ms ease, transform 150ms ease; }
#book:hover .btn { fill: #C9FF66; }
#book:active .btn { transform: scale(0.97); }
#book:focus-visible .btn { stroke: #FFFFFF; stroke-width: 3; }
Enter fullscreen mode Exit fullscreen mode

transform-box: fill-box matters: without it an SVG shape scales from the top-left corner of the drawing, not from its own center. Make the group keyboard reachable with tabindex="0" and a role, so :focus-visible has something to match.

A toggle driven by an attribute

.knob { transition: transform 200ms cubic-bezier(0.16, 1, 0.3, 1); }
#toggle[aria-checked="true"] .knob { transform: translateX(20px); }
Enter fullscreen mode Exit fullscreen mode

Flip aria-checked in three lines of script on click and on Space or Enter. The state then lives in the markup, so a screen reader can read it too.

An entrance, and a reduced-motion switch

@keyframes rise { from { opacity: 0; transform: translateY(8px); }
                  to   { opacity: 1; transform: none; } }
.h, .s { animation: rise 400ms ease-out both; }
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}
Enter fullscreen mode Exit fullscreen mode

What we measured

We ran the demo in headless Chrome on 2026-10-01, drove it with real mouse and keyboard events over the DevTools protocol and read back the computed styles:

  • Hover moved the fill from rgb(181, 255, 58) to rgb(201, 255, 102) with a 0.15 s transition.
  • Holding the mouse down computed the button's transform to a 0.97 scale; releasing it returned it to none.
  • A click set aria-checked to true, moved the knob 20 px and changed the track fill; Space on the focused toggle did the same.
  • Tab twice put focus on the button, :focus-visible matched and the ring computed to a 3 px white stroke.
  • With prefers-reduced-motion: reduce, the transition duration computed to 0 s and the heading had no running animations.

We did not test touch devices, Safari or Firefox.

Where CSS stops

CSS handles states and short entrances well. Past a few steps in a fixed order, or a drawn character that has to move as one connected piece, the timing turns into a pile of delays, and a dedicated animation tool earns its place. SVG Lab's separate animation product, the SVG Animate MCP, releases next week; the SVG Lab MCP designs the screens and does not animate them.

The live demo you can hover, press and tab through, plus the full FAQ, is on the SVG Lab blog: How do you prototype UI animation before building the app?

Top comments (0)