What you are looking at

Six toggles. On the left, three switches: click anywhere on one and it flips. On the right, three two-sided toggles: there you click the side you want. Each row has its own temperament: balanced, firm, and elastic to the point of being a cartoon.

Elastic toggles, the live component

Live, try it
The real component. Click through all six, then press and hold one without letting go.

All six are one machine with different numbers. This article builds it a layer at a time, starting from a toggle with no motion at all, so you can decide where to stop for your own project.

It just flips

What it does
Holds one true or false value and changes it when you click.
Why it exists
This is the whole job. Every layer after this one changes how it feels, never what it does.

Click them

Interactive
a checkbox
drawn as a switch
drawn with two sides

All three hold one true or false value and change it the moment you click. Nothing travels: the knob is in one place, then it is in the other.

They work, and they feel like nothing. The knob is in one place, then in the other, and your eye has to find it again.

ringthe edgeknobthe part that movestrackthe whole pilltravelrest to restgapequal on every sidefillthe on colourplatebehind the chosen sideicontracksideone of two targets
The names used from here on. A switch has one target and a click means the other state; a two-sided toggle has two targets and a click means this one.

It slides

What it does
Moves the knob across over a moment instead of placing it.
Without it
The change is a cut. The eye loses the knob and finds it again on the other side.

The same toggles, now sliding

Interactive
jumps
slides
slides

Now your eye can follow the knob across. Drag the duration up and it turns sluggish; around 200 ms it reads as instant but connected.

One level deeper. The knob moves with transform: translateX(), never with left: a position property makes the browser lay the page out again on every frame, a transform only moves a finished picture. And it leaves at full speed and slows into the stop. A curve that winds up slowly feels like the toggle hesitated.

This is the firm row: 220 ms, no stretch, no overshoot. It is a complete toggle, and many interfaces should stop here.

It stretches

What it does
Lets the knob grow long as it leaves and shrink back as it lands, like something soft being pulled.
Why it exists
A rigid knob shows that it moved. A stretching one shows which way it went, and how fast.
the clickat full speedat restat rest, roundlongest when it is fastest:the front runs ahead of the backit slowed down and rounded offbefore it arrived
One flight of the balanced switch, to scale. The knob is as long as it is fast, so it is round again by the time it lands.

There is an obvious way to make a shape longer, and it is wrong. Try both in slow motion and watch the round ends.

Stretch it two ways

Interactive
one piece, scaled
two ends and a bar
one piece, scaled
two ends and a bar

Watch the ends while each one is long. The scaled knob turns into an egg and the scaled plate's corners smear; the built ones keep the same round ends at any length.

One level deeper. The obvious way is scaleX, but scaling stretches the corners too. So the shape is built from three pieces: two copies of itself and a plain bar between them. One copy stays, the other slides out, and the bar scales to fill the gap. The plate of the two-sided toggle is built the same way.

The motion is one move, not a stretch followed by a settle. The stretch follows the speed: longest when the knob is fastest, gone by the time it has slowed down. So the front never reaches the wall first and waits for the back to pile in behind it, which is what reads as a rubbery landing.

near endstays where it isbarscaleX(e / width)far endtranslateX(e)=a true capsulee px longer than a circle
The knob taken apart. Only the bar is ever scaled, and it is a rectangle, so nothing round is distorted.
components/live/elastic-toggles-1/engine.ts (abridged): the stretch follows the speed, and drawing a pose touches only transforms.
// The trailing edge makes the trip on a spring; the stretch follows its speed.const trail = trail0 + (trail1 - trail0) * springAt(t, bounce);let q = (reach * speed(t)) / top;// A calm feel lands round: its front edge keeps a share of the remaining// distance in hand, so front and back close in on the wall together.const left = dir * (trail1 - trail);const cap = Math.max(left, 0) * LANDING_SHARE; // 0.6const landed = (q * cap) / Math.cbrt(q * q * q + cap * cap * cap || 1);q = landed + (q - landed) * Math.min(feel.bounce / 0.5, 1);// Drawing a pose touches only transforms.function setPose(pose: Pose): void {  const u = scale();  mover.style.transform = `translateX(${pose.x * u}px)`;  body.style.transform = `scaleY(${pose.sy})`;  far.style.transform = `translateX(${pose.e * u}px)`;  bar.style.transform = `scaleX(${pose.e / base})`;}

It springs back

What it does
Decides how the knob comes to rest: dead on the spot, with one soft overshoot, or with a wobble.
Why it exists
This one number is most of what people mean by how a toggle feels.

How it comes to rest

Interactive
switch
two sides
Overshoot3%
Reads asa soft swing

At 0 the curve meets the rest line and stops. Raise it and the curve shoots past and swings back. The balanced and firm rows sit at 0; the elastic row lives at the far end.

One level deeper. The curve is a damped spring: a weight on a spring, released. bounce sets the damping. At 0 it reaches rest as fast as it can without passing it; above 0 it passes, swings back, and the swings die away. The elastic row adds squash: the knob is 22% flatter while it is long, and springs past its full height on the way home.

The spring the knob rides. A flight samples it into forty keyframes.
export function springAt(t: number, bounce: number): number {  if (t <= 0) return 0;  if (t >= 1) return 1;  const zeta = Math.max(0.08, 1 - Math.min(Math.max(bounce, 0), 0.92));  if (zeta >= 0.999) {    const w = 9.5;    return 1 - Math.exp(-w * t) * (1 + w * t);  }  const decay = 6.9; // e^-6.9 is 0.1%: settled  const wd = (decay * Math.sqrt(1 - zeta * zeta)) / zeta;  return 1 - Math.exp(-decay * t) * (Math.cos(wd * t) + (decay / wd) * Math.sin(wd * t));}

It answers the press

What it does
Reacts the moment the mouse button goes down, before the click is finished.
Why it exists
A click only counts when the button comes back up. Without this, the toggle ignores you until then.

Press and hold, then let go

Interactive
hold it down
hold the empty side

Hold the mouse button down without releasing. The knob leans toward where it will go and waits. Let go and it leaves from that pose; drag off before letting go and it springs back.

One level deeper. On pointer down the knob takes 150 ms to lean toward where it would go, and holds. Letting go commits from that pose, so press and release read as one gesture; dragging off springs it back. A switch always leans toward its other state. A two-sided toggle leans toward the side under the pointer, and if that side is already chosen it only squeezes a little.

The press. It is on by default; pressFeedback turns it off and pressAmount scales it.
// How far a held press leans, in base px. A stiffer feel leans less.const lean = (base, feel, amount) => amount * base * (0.18 + 0.4 * feel.stretch);function onPointerDown(event: PointerEvent): void {  const { enabled, amount } = press();  if (!enabled || amount <= 0) return;  const target = kind === "switch" ? !on : side === 1;  const now = part.current();  const p = lean(base, options.feel(), amount);  const pose: Pose =    target === on      ? { x: now.x, e: 0, sy: 1 - 0.14 * amount } // already there: a small squeeze, nothing more      : target        ? { x: now.x, e: p, sy: 1 - 0.06 * amount }        : { x: now.x - p, e: p, sy: 1 - 0.06 * amount };  held = true;  void part.fly([{ pose, ms: 150, easeX: GO, easeShape: GO }]);}

It cannot be stalled

What it does
Gives the whole flight to the browser in the click, so it plays even while the page is frozen.
Why it exists
Toggles switch things on, and whatever they switch on makes the page busy just as the knob is moving.
Without it
The knob stretches, then hangs there, long and stuck, until the page recovers.

Flip both while the page is busy

Interactive
second half started from script
one animation, handed over

The page freezes right after the flip, as it would while something heavy loads. The chained knob hangs stretched until the page comes back, because the second half of its flight is waiting for script. The other one never notices.

One level deeper. Script and layout share one thread. A second one, the compositor, can move and fade finished layers by itself, so a transform animation survives a frozen page, but only if it was described in full up front. The chained toggle starts the second half of its flight from script, and script cannot run while the page is busy. The fix is one animation per moving part, with the whole flight written out as keyframes in the click.

One animate() call per element covers the whole flight.
function animate(from: Pose, beats: Beat[]): Animation[] {  const u = scale();  const total = beats.reduce((sum, beat) => sum + beat.ms, 0);  let elapsed = 0;  const frames = [{ pose: from, offset: 0 }, ...beats.map((beat) => ({ pose: beat.pose, offset: (elapsed += beat.ms) / total }))];  // A keyframe's easing shapes the stretch of time up to the next keyframe.  const keys = (value, ease) =>    frames.map((frame, i) => ({ transform: value(frame.pose), offset: frame.offset, easing: beats[i] ? ease(beats[i]) : "linear" }));  const timing = { duration: total, fill: "forwards" };  return [    mover.animate(keys((p) => `translateX(${p.x * u}px)`, (b) => b.easeX), timing),    body.animate(keys(bodyTransform, (b) => b.easeHeight ?? b.easeShape), timing),    far.animate(keys((p) => `translateX(${p.e * u}px)`, (b) => b.easeShape), timing),    bar.animate(keys((p) => `scaleX(${p.e / base})`, (b) => b.easeShape), timing),  ];}

The small truths

What it does
Three corrections that nobody notices when they are right.

On and off are the same size. The edge is a ring drawn on top of everything, the same in both states. If the fill covered a border instead, the track would look larger when on and the knob smaller. The knob is placed by arithmetic, 4px from every side, so it stays centred at any scale.

Colour is a fade, not a repaint. The on colours already exist as layers over the resting ones, and turning on only fades them in. Opacity, like transform, belongs to the compositor. The on fill is deliberately quiet, so the bright knob carries the state.

Nothing leaves the track. Before each flight the bounce is lowered until its widest swing fits in the gap around the knob. The swing is sized to the room it has, not cut off by it. The elastic row keeps its wobble in its height, where there is space.

The room around the knob sets the limit for the swing.
// The most bounce, up to `wanted`, whose widest swing over `distance` stays within `room`.function bounceWithin(wanted: number, distance: number, room: number): number {  let bounce = Math.min(Math.max(wanted, 0), 0.92);  while (bounce > 0 && overshootOf(bounce) * distance > room) bounce -= 0.02;  return Math.max(bounce, 0);}// The trip rides this spring, so the part cannot swing past the gap around it.const bounce = bounceWithin(feel.bounce, trip, room);

Two toggles, three feels

Put the layers back together and the six toggles are four numbers per row. Balanced is stretch 0.45, squash 0, bounce 0 over 520 ms. Firm is 0, 0, 0 over 220 ms. Elastic is 1, 0.22, 0.8 over 1100 ms. With reduced motion switched on, all three skip the flight and place the knob.

That is the whole machine. Every number here is a control on the component's page, next to the presets, and the prompt there builds the switch by default, with the two-sided toggle and the full set as options.