
The elastic button
Buttons, Hover, Text
NewFree
The Book a call button of a studio, in the minimal style: a hairline pill with a light label. Under the pointer the letters take weight as if under a lens, each on its own spring, and the pill widens as much as the heavier letters need, its outline thickening with them. Pressed, every letter gets heavy in a wave that starts from the point you pressed and the pill gives one pixel; then the label thins down to a hairline, Booked arrives with a drawn blue tick, and the button comes back.
A folder that runs on its own: index.html, style.css, script.js and the libraries from a CDN. The React and Vue tabs hold the same demo as a ready component, which switches its animations off when it leaves the page.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>The button with weight under the finger</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/roboto-flex@5.3.0/grad.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the frame keeps the room of the widest the pill can get, so the preview never has to move -->
<div class="frame" data-frame>
<!-- the button: a hairline pill with a light label. Under the pointer the letters take weight as under a lens,
each on its own spring, and the pill widens as much as the heavier letters need. The letters, the outline
and the tick are set by the script on every frame -->
<button class="call" type="button" aria-label="Book a call" data-call data-autoclic>
<span class="call__pill" aria-hidden="true" data-pill></span>
<span class="call__line" aria-hidden="true" data-line>
<svg class="call__tick" viewBox="0 0 20 20" data-tick><path d="M4.4 10.4l3.7 3.7 7.6-8" pathLength="1" data-tick-path/></svg>
</span>
</button>
</div>
<p class="sr" role="status" aria-live="polite" data-said></p>
<!-- a hidden copy of the type, to measure every letter at every weight -->
<div class="ruler" aria-hidden="true" data-ruler></div>
</main>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
<script src="script.js"></script>
</body>
</html>Change values, colours and text with the controls and watch the animation respond live. Or write in the code yourself. Then download the folder with your changes.
18 values to change
With the Animorio connector, Claude Code takes it from here, guide included, and fits it into your project. Paste this request:
Take “The button with weight under the finger” from Animorio and fit it into my siteRoboto Flex is a variable typeface with a weight axis from 100 to 1000, and as the weight changes the letters get wider. At the start a hidden ruler sets every letter of the two words at ten weights and measures its advance with getBoundingClientRect; the advance at any weight comes from interpolating between the two nearest. The letters are separate elements and lose their kerning: the pairs (Bo, ok, ca…) are measured on a canvas in the same face, ab minus a minus b, and put back by hand.
The lens is a Gaussian bell around the pointer, LENS pixels wide: every letter wants the weight WEIGHT[0] plus the share of WEIGHT[1] the bell gives it at the distance of its centre from the pointer. The strength of the lens is a number GSAP takes to 1 when the pointer comes in and to 0 when it leaves, so the lens fades on and off with no jump.
A letter does not jump to the weight it wants: it is a damped spring on the weight axis, with stiffness and damping in SPRING, integrated in half steps with the time step capped at 1/30 of a second. Under 1 the damping lets the weight go just past and brings it back; and since every letter has its own spring, a quick pass leaves a wave that settles letter by letter.
On every frame one function on the GSAP ticker adds up the advances of the letters at their weight of the moment, with the pairs, centres the line and puts each letter in its place with a translate3d and its font-variation-settings. The pill is as wide as the line plus the margin, but follows it on a second spring (PILL); under a press it gives SQUASH pixels in height and takes them in width. The outline is an inset box-shadow that goes from OUTLINE[0] to OUTLINE[1] with the average weight of the letters. When springs and tweens are still the ticker removes itself.
On a press every letter gets a tween of its press towards 1, delayed by its distance from the point pressed divided by WAVE: a wave of weight that starts from there. On release the same wave, from the same point, takes the resting weight down to the hairline, 100, and fades the letters; then Booked arrives: the tick is drawn with stroke-dashoffset and its stroke thickens with a back.out, and the letters come up from the hairline in a wave that starts from the tick, each with a knock of weight as it settles. After HOLD seconds Book a call comes back, from the centre.
A finger has no over: the lens lands where it touches and the wave starts from there. With the Tab key the lens turns on at the centre and there is a blue ring; Enter or Space presses. With reduced motion the weight does not change and the GRAD axis works instead, the grade, which thickens the letters without widening them: nothing moves, and the words change with a fade.
| Name | File | Default | What it changes |
|---|---|---|---|
WORDS | script.js | ['Book a call', 'Booked'] | The two words: at rest, and once the call is booked (with the tick in front). |
WEIGHT | script.js | [340, 860] | The weight of the letters at rest, and under the lens or the press (Roboto Flex goes from 100 to 1000). |
LENS | script.js | 21 | The radius of the lens in pixels: how many letters it weighs at once. |
SPRING | script.js | [300, 0.58] | Each letter’s spring: its stiffness and its damping (under 1 it goes just past and comes back). |
PILL | script.js | [420, 0.7] | The pill’s spring, with the same two numbers. |
WAVE | script.js | 520 | The speed of the wave of weight that starts from the point pressed, in pixels a second. |
HOLD | script.js | 1.8 | The seconds Booked stays before the button comes back. |
OUTLINE | script.js | [1, 2] | The pill’s outline in pixels, with the letters light and with all of them heavy. |
SQUASH | script.js | 1 | The pixels the pill gives under a press. |
For the main button of the site of a studio, a consultant or a photographer: booking a call, asking for a quote, getting in touch. It suits a light, empty page, where a light label and a hairline are enough. It needs a variable typeface with a wide weight axis: with a static typeface only the pill is left.
The pill is the same, 60 high, and fits easily in 390 pixels even with every letter heavy. Without a pointer the lens lands where the finger touches, the wave of weight starts from there and the tap starts the whole round, with the same animations as on a computer.
The weight does not change, so the letters never move: under the pointer and on a press the GRAD axis thickens them all together while they keep their width, with a fade. The words change with a cross-fade, and the tick appears already drawn.
GSAP 3.15.0, Roboto Flex (font variabile, con gli assi del peso e della gradazione) 5.3.0.
Compressed with gzip, the three demo files weigh 8.2 KB; the libraries: GSAP 28 KB.
Tested on 16 October 2026
Use it in your own projects and in your clients’ projects. Do not resell it or redistribute it as is. GSAP stays under its own Standard license. Full license.
With your account’s email and password.
or with your email
You’re signed in as .
With your account, you can download the zip of every animation and every pack, for free.