Skip to content
animorio
IT
All components

The cardboard box button

Buttons, 3D, PhysicsCSS only, GSAPAdvancedCompressed weight: Demo 13 KBGSAP 28 KBFreeDownload the zip

The Add to cart button of a small shop, with an open corrugated cardboard box in the round end of the pill, made in real CSS 3D: floor, walls and four flaps, each with its inside and its outside. The flaps ride on real springs and shiver towards the pointer. At a click a candle jar drops into the box, the flaps close one after the other, slapping down and bouncing, a strip of tape runs along the seam and the box hops: Added, Ships tomorrow. Another click cuts the tape, the flaps spring open and another candle goes in: 2 in cart.

The code

A folder that runs on its own: index.html, style.css, script.js and the libraries from a CDN. The React tab holds 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 cardboard box button</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/bricolage-grotesque@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the cardboard box button: an Add to cart pill with an open corrugated box in its round right end, built in CSS 3D
         with its four flaps up. A click drops a candle jar into the box, the flaps close one after the other on springs
         and bounce, a strip of tape runs over the seam and the box hops: Added, Ships tomorrow. Another click cuts the
         tape, the flaps spring open and one more candle goes in. The flaps shiver towards the pointer -->
    <div class="pack" data-pack>
      <button class="pack__btn" type="button" data-btn data-autoclic>
        <span class="pack__fill" aria-hidden="true"></span>
        <span class="pack__faces">
          <span class="pack__face" data-face="rest">
            <span class="pack__word" data-wide="Add to cart" data-rest-text>Add to cart</span>
            <span class="pack__price" data-price-text>€32</span>
          </span>
          <span class="pack__face pack__face--busy" data-face="busy" aria-hidden="true">
            <span class="pack__shine" data-busy-text>Packing…</span>
          </span>
          <span class="pack__face pack__face--done" data-face="done" aria-hidden="true">
            <span class="pack__lines">
              <span class="pack__lead">
                <svg class="pack__tick" viewBox="0 0 20 20" aria-hidden="true"><path d="M4.6 10.4l3.5 3.5 7.4-8" pathLength="1" data-tick/></svg>
                <span data-lead-text>Added</span>
              </span>
              <span class="pack__ship" data-ship-text>Ships tomorrow</span>
            </span>
          </span>
        </span>

        <!-- the box: a perspective, a layer that hops, and the box turned towards you, every wall and flap a plane with
             an outside and an inside -->
        <span class="crate" aria-hidden="true" data-crate>
          <span class="crate__shadow" data-shadow></span>
          <span class="crate__hop" data-hop>
            <span class="crate__rig">
              <span class="wall wall--floor"><span class="side side--in"></span></span>
              <span class="wall wall--back"><span class="side side--out"></span><span class="side side--in"></span></span>
              <span class="wall wall--left"><span class="side side--out"></span><span class="side side--in"></span></span>
              <span class="wall wall--right"><span class="side side--out"></span><span class="side side--in"></span></span>
              <span class="jars" data-jars></span>
              <span class="wall wall--front"><span class="side side--out side--print"></span><span class="side side--in"></span></span>
              <span class="flap flap--left" data-flap="left"><span class="side side--out"></span><span class="side side--in"></span></span>
              <span class="flap flap--right" data-flap="right"><span class="side side--out"></span><span class="side side--in"></span></span>
              <span class="flap flap--back" data-flap="back"><span class="side side--out"><span class="tape tape--half" data-tape></span></span><span class="side side--in"></span></span>
              <span class="flap flap--front" data-flap="front"><span class="side side--out"><span class="tape tape--half" data-tape></span></span><span class="side side--in"></span></span>
              <span class="tab tab--left" data-tab><span class="tape" data-tab-tape></span></span>
              <span class="tab tab--right" data-tab><span class="tape" data-tab-tape></span></span>
              <span class="cut" data-cut></span>
            </span>
          </span>
        </span>
      </button>
      <p class="sr" aria-live="polite" data-live></p>
    </div>
  </main>

  <!-- the candle that goes in, copied by the script for each one -->
  <template data-jar>
    <span class="jar">
      <svg class="jar__art" viewBox="0 0 30 40" aria-hidden="true">
        <defs>
          <linearGradient id="jar-glass" x1="0" x2="1">
            <stop offset="0" class="glass-edge"/>
            <stop offset="0.18" class="glass"/>
            <stop offset="0.32" class="glass-lit"/>
            <stop offset="0.5" class="glass"/>
            <stop offset="0.85" class="glass-deep"/>
            <stop offset="1" class="glass-edge"/>
          </linearGradient>
          <linearGradient id="jar-wax" x1="0" x2="1">
            <stop offset="0" class="wax-deep"/>
            <stop offset="0.4" class="wax"/>
            <stop offset="1" class="wax-deep"/>
          </linearGradient>
        </defs>
        <path d="M5 6.5h20v27.5q0 3.5-3.5 3.5h-13q-3.5 0-3.5-3.5z" fill="url(#jar-glass)"/>
        <ellipse cx="15" cy="9" rx="9.2" ry="2.3" fill="url(#jar-wax)"/>
        <path class="jar__wick" d="M15 9V5.6"/>
        <rect class="jar__label" x="5" y="17" width="20" height="11"/>
        <path class="jar__ink" d="M9 21.2h12M10.5 24.2h9"/>
        <path class="jar__gleam" d="M7.4 8.5v25"/>
        <ellipse class="jar__lip" cx="15" cy="6.5" rx="10" ry="2.2"/>
      </svg>
    </span>
  </template>

  <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.

33 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 cardboard box button” from Animorio and fit it into my site

What it does

On a white background an espresso pill 60 pixels tall, with a thread of light along the top and a soft shadow under it. On the left, in cream Bricolage Grotesque, Add to cart and the price €32 in a small pill; in the round right end an open corrugated cardboard box, turned towards you and seen a little from above, so you look inside: the lighter front face with two printed arrows (this side up), the right side in shadow, the darker floor and inner walls, and the four open flaps, the front one hanging forward, the right one out, the left and back ones up. The board has its flutes faintly showing and the cut edge lighter on the flaps; the light on each flap changes with its angle. On arrival the box is shut and the flaps spring open one after the other, rocking on their springs. With the mouse over the button the words widen, the fill swells with a bounce and the flaps shiver and rise a little towards the pointer, the one facing it the most. At a click Packing… arrives with a sheen running through it, and from above falls a candle jar of amber glass with pale wax, a wick and a cream label: it drops into the box, squashes a little landing and the box gives under the weight. Then the flaps close one after the other, the two short ones first and the two long ones over them, and each slaps down on the box and bounces before it stops; a terracotta tape runs down the left end, along the seam and down the right end, and the box hops and squashes as it lands: on top, small, a tick that draws itself and Added, under it Ships tomorrow. Another click runs a blade along the tape, the flaps spring open carrying the two cut halves, the old tape comes away and another candle drops in: this time the small line says 2 in cart. With the keyboard the focus shows an espresso ring, the words widen and the flaps give a start; Enter or Space add. On a phone the finger on the button makes the flaps shiver and the tap adds.

How it works

The box in CSS 3D

The box is a group of spans in transform-style: preserve-3d under a 360 pixel perspective, turned by rotateX(-32deg) rotateY(-34deg). The floor, the four walls and the four flaps are planes put in place with translateZ, translateX and rotateY from the middle of the box; each plane has two faces, the outside and the inside turned half round, with backface-visibility: hidden, so from either side you see the right board. The measures (--bw, --bd, --bh, --fl) live in :root and the script reads them back. The corrugated board is a fine repeating-linear-gradient over the kraft colour; the light of each face is a dark layer with the opacity --shade.

The flaps on springs

Each flap is hinged on the top edge of its wall (transform-origin: 50% 100%) and turns with rotateX: 0 straight up, 90 shut on the box, negative numbers open outwards. Its angle follows a damped spring worked out every frame, in steps of 1/240 of a second (SWING swings a second, DAMPING); to shut, the target is a little past 90 degrees, so the flap slaps on the stop at 90 and bounces off keeping part of its speed (BOUNCE), then stays pressed down. The long flaps, nearly shut, rise by 0.9 pixels to lie over the short ones. The light on the outside is the dot product of its normal (the wall's, turned up by the angle) with the lamp's direction.

The shiver towards the pointer

Each flap knows which way it faces on the screen. With the pointer on the button its target rises by 6 degrees plus 22 for how much it faces the pointer, and a shiver of gradient noise on two frequencies (9 and 23 a second) is added, stronger for the flap that faces it; all of it times SHIVER and times how much the pointer is on the button, which GSAP eases up and down. The springs do the rest: the flaps tremble without jerks.

The candle dropping in

The candle is an SVG (amber glass in a gradient, wax, wick, label) on a plane inside the box's 3D space, turned the opposite way to the box (rotateY(34deg) rotateX(32deg)) so it always faces you; the browser sorts it in depth with the walls, so going in it disappears behind the front face. It falls with an acceleration (power2.in), squashes and comes back on an elastic, and the box gives at the same moment: a scaleY from its floor that comes back with elastic.out. There are two places on the floor: a new candle takes the oldest one's, which lies under the shut flaps and goes unseen.

The tape, the cut and the hop

The tape is made of four pieces: two halves on the long flaps, along their free edge, and two tabs on the ends. The halves stretch together with scaleX after the left tab and before the right one, and read as a single tape. Living on the flaps, when the box opens again the two halves go each with its flap, like a cut tape; first a pale blade runs along the seam. The box hops on a layer of its own (y −12 and back with power2), and squashes from its floor as it lands; the shadow under it narrows while it is in the air.

The words and the states

Three faces on top of each other, as wide as their words: at rest with the invisible wide copy for the widening under the mouse (font-stretch from 86 to 100 percent), Packing… with its sheen, and the final face on two lines. The button is a real button: aria-disabled while it packs, an aria-live region says Packing Fig & cedar candle… and then Added. 1 in cart. Ships tomorrow., and the aria-label says the next click adds another. The first round takes 2.1 seconds and the later ones 2.6, less than the 2.8 between two clicks of the preview.

Values to change

NameFileDefaultWhat it changes
LABELscript.jsAdd to cartThe words at rest. If they are too long for the room, the type gets smaller.
PRICEscript.js€32The price, in its small pill next to the words.
BUSYscript.jsPacking…The words while the candle goes in and the box shuts.
ADDEDscript.jsAddedThe small line the first time, after the tick.
UNITscript.jsin cartThe small line from the second time, after the number.
SHIPscript.jsShips tomorrowThe big line once the box is shut: in a real site your shop says the date.
PRODUCTscript.jsFig & cedar candleThe product, for the aria-live region: in a real site it is the one on the page, and the adding has to be wired to your cart.
SWINGscript.js3.4The flaps' swings a second on their springs: higher, stiff board and quick snaps; lower, soft slow flaps.
DAMPINGscript.js0.26How hard the flaps are held back: 0.08 they flap and rock a long time, 1 they go straight to their place.
BOUNCEscript.js0.38How much speed a flap keeps when it slaps on the box: 0 it stops dead, 0.8 it bounces several times.
SHIVERscript.js1How much the flaps shiver and rise towards the pointer: 0 they stay still.
OPENscript.js[-72, -34, -44, -66]The open flaps' angles, in degrees past upright: front, back, left, right. Past −60 a flap hangs outwards and shows its inside.

Try them in the Playground

When to use it

For the Add to cart button of a shop that ships: candles, ceramics, tea, cosmetics, books, handmade things. The box shutting tells the shipping, and the Ships tomorrow line is the right place for the real delivery date. It has to be wired to your cart and your shop's date: here it is all simulated.

On phones

The pill is about 260 pixels wide: at 390 it fits with room to spare, and if the words do not fit the type gets smaller. A finger on the button makes the flaps shiver towards it and the tap adds; the box keeps its size.

With reduced motion

No springs and no hops: the flaps stay still and open, they do not shiver, the fill does not swell and the words do not widen (under the mouse the fill only lightens a little). At a click the box's faces fade out and come back already shut, taped, with the candle inside, while Packing… and then Added, Ships tomorrow fade in. Another click does the same and the number goes up.

Pitfalls

  • In reduced motion the box faded with the opacity of the perspective's container: an opacity under 1 flattens the 3D, and for a moment the faces were drawn in the code's order, with the box looking shut. Now only the flat planes fade (the faces and the candles), and the 3D stays whole.
  • Also in reduced motion the flaps headed for shut on arrival, because the springs' starting target was shut (with motion the box comes in shut and opens). Now without motion the target is open and the spring is skipped: the flap goes straight to its angle.
  • The front flap open at −46 degrees was seen edge on from this point of view: a line. Open at −72 it hangs forward and shows its inside, and the right one at −66 does the same on the side.
  • The long flaps, shut on the same plane as the short ones, flickered (z-fighting). They rise by 0.9 pixels as they shut, between 55 and 88 degrees, and stay on top.
  • In a GSAP timeline the position '+=0.14' counts from the end of the timeline, not from the last add: the flaps shut late and the round took 3.3 seconds, more than the preview's 2.8. Now every step has an absolute time from an origin B, which is 0.5 seconds when the box has to be opened first.
  • With the box seen at 27 degrees the candle inside hardly showed: looking at it at 32 degrees the floor shows, and the candle has its top in the light.

Libraries

GSAP 3.15.0, Bricolage Grotesque (font variabile) 5.3.0.

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

Testing

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