Skip to content
animorio
IT
All components

The button with weight under the finger

Buttons, Hover, TextGSAP, CSS onlyAdvancedCompressed weight: Demo 8.2 KBGSAP 28 KBFreeDownload the zip

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.

The code

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>

Playground

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

Or ask Claude Code for it

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 site

What it does

On a white page there is only a pill 60 pixels high, white, with a one-pixel black outline and the label Book a call in Roboto Flex, light, at weight 340. When the page opens the label comes up from a very thin hairline, one letter after the other from the centre outwards, and each letter takes a little extra weight for an instant before it settles. With the mouse over the button the letters take weight as if under a lens: the one right under the pointer goes almost to 860, its neighbours a little less, the far ones stay light, and moving the mouse slides the lens along the word. Every letter reaches its weight on a damped spring of its own that goes just past and comes back, so a pass leaves a wave behind that settles one letter after the other. Heavier letters are also wider: the pill widens as much as it takes, on a spring as well, and its outline gets a hair thicker. A press makes every letter heavy, the ones near the point you pressed first and then the others, the pill widens again, the outline reaches two pixels and the pill squashes by one pixel. Letting go books the call: from that same point the label thins down to a hairline and fades, the pill closes on the new word, a blue tick is drawn in front and then takes weight too, and Booked comes up from the hairline in a wave that starts from the tick. After a moment the word goes back to Book a call the same way. On a phone the lens lands where the finger touches, and the wave of weight starts from there.

How it works

The type measured once

Roboto 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

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.

One spring per letter

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.

The line and the pill

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.

The wave of the press

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.

Finger, keyboard and reduced motion

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.

Values to change

NameFileDefaultWhat it changes
WORDSscript.js['Book a call', 'Booked']The two words: at rest, and once the call is booked (with the tick in front).
WEIGHTscript.js[340, 860]The weight of the letters at rest, and under the lens or the press (Roboto Flex goes from 100 to 1000).
LENSscript.js21The radius of the lens in pixels: how many letters it weighs at once.
SPRINGscript.js[300, 0.58]Each letter’s spring: its stiffness and its damping (under 1 it goes just past and comes back).
PILLscript.js[420, 0.7]The pill’s spring, with the same two numbers.
WAVEscript.js520The speed of the wave of weight that starts from the point pressed, in pixels a second.
HOLDscript.js1.8The seconds Booked stays before the button comes back.
OUTLINEscript.js[1, 2]The pill’s outline in pixels, with the letters light and with all of them heavy.
SQUASHscript.js1The pixels the pill gives under a press.

Try them in the Playground

When to use it

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.

On phones

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.

With reduced motion

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.

Pitfalls

  • Under the lens the heavier letters get wider: without the line set again on every frame from the measured numbers, neighbouring letters would have overlapped, or the word would have trembled while the browser measured it at every step.
  • In the first test the letters faded while they were still heavy, and the hairline never showed: the label vanished grey and bold. Now every letter first thins down to 100 and only 0.22 seconds later starts to fade; and the tick is drawn with a power2.out, so it shows at once and the pill stays empty only for a moment.
  • Fontsource’s index.css only loads the weight axis; reduced motion needed the grade too, so the typeface comes from grad.css, which has wght and GRAD in a single 57 KB file instead of the 326 of full.css.
  • A click from the keyboard comes with detail equal to 0, like the preview’s scripted one: to tell them apart the button notes the key pressed, and only a scripted click with no key starts the demo round.
  • A ticker that turns itself off when nothing moves does not see the delayed tweens of the wave start: every tween and timeline wakes it from its onUpdate.

Libraries

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.

Testing

Tested on 16 October 2026

  • Desktop: Done
  • Phone: Done
  • Reduced motion: Done
  • Safari on iPhone: To do

License

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.

All components