Skip to content
animorio
IT
All components

The honey button

Buttons, SVG, PhysicsGSAPAdvancedCompressed weight: Demo 7.0 KBGSAP 28 KBFreeDownload the zip

A pill of honey with a drop hanging under it: a glint of light slides over its surface after the pointer, the drop swells and stretches as you come near, and on the click the thread thins and snaps, the drop falls, splashes and spreads into a small pool. Then a new drop grows back out of the pill.

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 honey button</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/geist@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the honey button: an amber pill of real-looking honey with a drop hanging under it. A light glides over its
         surface after the pointer, the drop swells and stretches when you come near, and pressing lets it go: the
         thread thins and snaps, the drop falls, splashes and settles into a small pool, the words say "Added to
         cart", and a moment later a new drop grows back out of the pill -->
    <div class="honey" data-honey>
      <svg class="honey__svg" aria-hidden="true" data-svg>
        <defs>
          <linearGradient id="honey-fill" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="0" y2="260" data-fill></linearGradient>
          <filter id="honey-goo" filterUnits="userSpaceOnUse" x="-64" y="-24" width="360" height="340" color-interpolation-filters="sRGB" data-filter>
            <feGaussianBlur in="SourceGraphic" stdDeviation="3.4" result="blur"/>
            <feColorMatrix in="blur" type="matrix" values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 20 -9" result="goo"/>
            <feGaussianBlur in="goo" stdDeviation="1.7" result="bump"/>
            <feDiffuseLighting in="bump" surfaceScale="5" diffuseConstant="1.3" lighting-color="#ffffff" result="shade">
              <fePointLight x="116" y="-60" z="140" data-light-a/>
            </feDiffuseLighting>
            <feComponentTransfer in="shade" result="soft">
              <feFuncR type="linear" slope="0.5" intercept="0.55"/>
              <feFuncG type="linear" slope="0.5" intercept="0.55"/>
              <feFuncB type="linear" slope="0.5" intercept="0.55"/>
            </feComponentTransfer>
            <feComposite in="goo" in2="soft" operator="arithmetic" k1="1" k2="0" k3="0" k4="0" result="lit"/>
            <feSpecularLighting in="bump" surfaceScale="5" specularConstant="1.1" specularExponent="26" lighting-color="#fff4cf" result="glint" data-glint>
              <fePointLight x="116" y="-60" z="140" data-light-b/>
            </feSpecularLighting>
            <feComposite in="glint" in2="goo" operator="in" result="glint-in"/>
            <feComposite in="lit" in2="glint-in" operator="arithmetic" k1="0" k2="1" k3="1" k4="0" result="honey"/>
            <feDropShadow in="honey" dx="0" dy="10" stdDeviation="7" flood-color="#a35a00" flood-opacity="0.28"/>
          </filter>
        </defs>
        <g filter="url(#honey-goo)" data-goo>
          <rect data-pill x="0" y="0" width="232" height="60" rx="30" fill="url(#honey-fill)"/>
        </g>
      </svg>
      <button class="honey__btn" type="button" aria-label="Order a jar" data-btn data-autoclic>
        <span class="honey__word" aria-hidden="true" data-word>Order a jar</span>
        <span class="honey__word honey__word--done" aria-hidden="true" data-done>
          <svg class="honey__tick" viewBox="0 0 20 20"><path data-tick d="M4.5 10.5l3.6 3.6 7.4-8.2"/></svg>
          <span data-done-text>Added to cart</span>
        </span>
      </button>
      <p class="sr" aria-live="polite" data-live></p>
    </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.

20 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 honey button” from Animorio and fit it into my site

What it does

On a white background a pill 60 pixels tall of glossy amber honey, with a dark edge that gives it volume and a drop hanging below it on a thin thread. Under the mouse a glint of light follows the pointer over the surface, the drop swells and the thread lengthens by another half, and the drop swings on a soft spring, leaning towards the pointer. Pressing flattens the pill; on the click the drop falls: the thread thins as the drop speeds up, snaps, the stub folds back into the pill and a tiny drop falls from the middle of the thread. Landing, the drop throws six droplets and spreads into a wide, low pool that rings for a moment. The words roll up into “Added to cart” with a tick that draws itself. After two and a half seconds the pool is taken back and a new drop comes out of the pill with a bounce while the words roll back. With the keyboard it behaves the same.

How it works

The honey is an SVG group with a filter

A rounded rectangle, an ellipse for the drop, a row of twelve circles for the thread, the pool and the droplets all sit in one group. The filter blurs the group by 3.4 pixels and then cuts the edge hard with a colour matrix on the alpha (times 20, minus 9): shapes that touch melt together like metaballs and the edges come back sharp.

The light

The result of that filter, blurred a little more, is the height map for two lights from the same point: a diffuse one that gives the volume (raised by a floor with a feComponentTransfer, or the far side goes black) and a specular one, the glossy glint. The script moves the light point towards the pointer and returns it to the top at rest.

The drop is a spring, then a falling body

At rest the drop follows a spring to the wanted length (longer when the pointer is near) and another one sideways, which bends it towards the pointer. On the click the springs are switched off and gravity and speed take over. The ellipse stretches with speed while keeping the same area.

The thread

Twelve circles between the foot of the pill and the drop, bigger at both ends and thinner in the middle. The middle radius falls with the length (a stand-in for volume being kept); when it falls below 1.3 the thread snaps: a stub folds back into the pill and a tiny drop leaves from the middle.

The landing

The pool is an ellipse with two under-damped springs, one for the width and one for the height, so it rings on contact. The droplets are bodies with a lighter gravity; when they reach the ground they nudge the pool’s spring. After HOLD seconds the pool is taken to zero and the new drop grows from the pill, with the spring bouncing it.

Values to change

NameFileDefaultWhat it changes
LABELscript.js'Order a jar'The words at rest.
DONEscript.js'Added to cart'The words after the drop has landed.
DRIPscript.js[32, 50]The length of the hanging drop at rest and with the pointer near, in pixels.
GRAVITYscript.js2600How hard the drop falls.
HOLDscript.js2.6The seconds the pool stays before the honey is taken back.
PUDDLEscript.js30The half-width of the pool, in pixels.
SPLASHscript.js6How many droplets the landing throws.
GROUNDscript.js230How far below the pill the pool forms, in pixels.
SHINEscript.js[0.85, 30]The glossy glint: how strong and how sharp.
COLORSscript.js['#ffcb3d', '#f39a00', '#c9700a']The honey, from the top to the foot.

Try them in the Playground

When to use it

For a purchase, an order or an “add” where the product is something to eat, cook or give, and the button can have a material and a gesture.

On phones

Taps work like clicks. With a finger there is no glint following the pointer and no drop that stretches as you come near; the fall and the pool are there. The low part of the scene (about 230 pixels under the pill) must fit on the screen.

With reduced motion

No springs, no fall, no splashes and no moving glint. On the click the drop is gone, the pool is already in place and the words swap with a fade; then the drop comes back.

Pitfalls

  • Diffuse light multiplied onto the colour turns the edges that face away from the light black: it must be raised by a floor (feComponentTransfer, slope 0.5 and base 0.55).
  • A thread thinner than about a pixel and a half vanishes in the filter: the thread snaps before it disappears, not after.
  • The filter region is in the SVG’s coordinates, not the shape’s: left at the default the falling drop is cut at the edge.
  • The click must not wait for the drop to grow: a click that arrived in the first second was ignored and the automatic preview saw nothing.
  • Pool, droplets and drop are drawn with one set of reused elements: creating new ones at every click makes the group grow and the filter slow down.

Libraries

GSAP 3.15.0, Geist (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 7.0 KB; the libraries: GSAP 28 KB.

Testing

Tested on 6 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