2026

Spring Tuner

Spring easing for CSS, tuned by feel: drag two sliders, watch real components move, copy pure CSS.

Stack

Next.js · CSS linear()

Type

Personal Project

Year

2026

A spring easing generator that you tune by watching rather than by reading numbers. Two sliders drive six real UI components, and the output is plain CSS — no animation library at the other end.

Overview

CSS transition timing historically couldn't overshoot. cubic-bezier is confined to a monotonic ease, so anything with real spring physics — the small bounce past the target and back — meant reaching for a JavaScript animation library.

The linear() timing function changed that. It accepts a list of sampled points, so a spring curve can be solved ahead of time and handed to CSS as data. Spring Tuner is the tool for producing that list: tune the motion until it feels right, then take the CSS.

What I built

  • Built the curve solver that samples spring physics into a linear() point list
  • Designed the two-slider control scheme, so tuning is a feel exercise rather than a physics one
  • Wrote a six-component preview grid so a curve can be judged on real UI instead of a bouncing square
  • Added a curve graph and an export panel for reading the maths behind whatever was tuned by eye

Feel first, numbers second

Stiffness and damping are the wrong handles for most people, because neither maps cleanly onto what you're trying to achieve — you want to know whether it feels snappy or loose, not what its damping ratio is. The sliders are arranged around the felt qualities, and the graph and numeric output are there afterward for anyone who does want them.

Six previews, because one lies

A curve that looks great on a bouncing ball can feel wrong on a modal, a toggle, or a list item, because the distance travelled and the element's size change how the same overshoot reads. Showing six different components moving on one curve at once is the fastest way to catch a setting that only works in isolation.

The output is just CSS

The whole point is leaving with nothing attached. What you copy is a transition-timing-function that runs anywhere CSS runs, with no runtime, no dependency, and no bundle cost — which is the argument for linear() over shipping a spring library in the first place.