Skip to content
animorio
IT
All components

The clothespin button

Buttons, SVG, HoverGSAPMediumCompressed weight: Demo 4.6 KBGSAP 28 KBFreeDownload the zip

In the minimal style, on white: the Save for later of a recipe site, with a small wooden clothespin clipped on the edge of the pill. Under the pointer the pin leans and parts its jaws a little. Pressed, the pin opens round its steel spring, a paper tag slides into the jaws and the pin snaps shut on it: Saved. Pressed again, it lets the tag go.

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 clothespin button</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/archivo@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the clothespin button: the Save for later of a recipe site, on white. A small beech clothespin is clipped on
         the top edge of the pill. Pressed, the pin opens, a paper tag slides into its jaws and the pin snaps shut on it:
         Saved. Pressed again, the pin lets the tag go -->
    <div class="pin" data-pin>
      <div class="pin__frame" data-frame>
        <svg class="pin__art" viewBox="-60 -110 120 150" aria-hidden="true" data-art>
          <defs>
            <linearGradient id="legno" x1="0" x2="1" y1="0" y2="0">
              <stop offset="0" stop-color="#c79f6c"/>
              <stop offset="0.35" stop-color="#e3c595"/>
              <stop offset="0.7" stop-color="#d6b27f"/>
              <stop offset="1" stop-color="#b98d58"/>
            </linearGradient>
            <linearGradient id="acciaio" x1="0" x2="0" y1="0" y2="1">
              <stop offset="0" stop-color="#e9ecef"/>
              <stop offset="0.45" stop-color="#9aa1a8"/>
              <stop offset="1" stop-color="#5d646b"/>
            </linearGradient>
            <filter id="morbida" x="-50%" y="-50%" width="200%" height="200%"><feGaussianBlur stdDeviation="2.4"/></filter>
          </defs>
          <g data-tag class="pin__tag">
            <rect x="-22" y="-10" width="44" height="58" rx="2.5" class="pin__tag-carta"/>
            <rect x="-22" y="40" width="44" height="2.6" class="pin__tag-riga"/>
            <text x="0" y="29" text-anchor="middle" class="pin__tag-testo">SAVED</text>
          </g>
          <ellipse cx="3" cy="8" rx="18" ry="5" class="pin__ombra" filter="url(#morbida)" data-shadow/>
          <g transform="scale(1.6 1)">
          <path class="pin__notch" d="M-3 -1 L3 -1 L3 5 L-3 5 Z M-0.8 5 L0.8 5 L0.8 17 L-0.8 17 Z"/>
          <g data-arm="l" class="pin__arm">
            <path class="pin__wood" d="M -8.2 -96 C -13.4 -95 -14.41 -88 -13.23 -78 L -8.2 -22 C -8 -14 -9.6 -8 -9.4 -2 L -8.4 14 C -8.2 17 -6.4 18.6 -3.8 18.2 L -0.6 17.4 L -0.6 8 L -3.4 5 L -3.4 -1 L -0.6 -4 L -0.6 -33 Z"/>
            <path class="pin__grain" d="M -11.62 -86 C -9.26 -60 -6.09 -40 -6.2 -24 M -9.81 -90 C -8.04 -70 -5.08 -50 -4.2 -30 M -7.2 0 C -7 6 -6.8 10 -6.4 14"/>
            <path class="pin__edge" d="M -8.1 -95 L -0.6 -4"/>
          </g>
          <g data-arm="r" class="pin__arm">
            <path class="pin__wood" d="M 8.2 -96 C 13.4 -95 14.41 -88 13.23 -78 L 8.2 -22 C 8 -14 9.6 -8 9.4 -2 L 8.4 14 C 8.2 17 6.4 18.6 3.8 18.2 L 0.6 17.4 L 0.6 8 L 3.4 5 L 3.4 -1 L 0.6 -4 L 0.6 -33 Z"/>
            <path class="pin__grain" d="M 11.62 -86 C 9.26 -60 6.09 -40 6.2 -24 M 9.81 -90 C 8.04 -70 5.08 -50 4.2 -30 M 7.2 0 C 7 6 6.8 10 6.4 14"/>
            <path class="pin__edge pin__edge--r" d="M 8.1 -95 L 0.6 -4"/>
          </g>
          <g class="pin__spring" data-spring>
            <path class="pin__wire" d="M-10.5 -40 C-13 -44 -12 -50 -9.6 -52 M10.5 -40 C13 -44 12 -50 9.6 -52"/>
            <rect x="-11.5" y="-40" width="23" height="9" rx="4.5" class="pin__coil"/>
            <path class="pin__turns" d="M-7.5 -40 V-31 M-4 -40 V-31 M-0.5 -40 V-31 M3 -40 V-31 M6.5 -40 V-31"/>
            <path class="pin__shine" d="M-9.5 -38.4 H9.5"/>
          </g>
          </g>
        </svg>
        <button class="pin__btn" type="button" data-btn data-autoclic aria-pressed="false">
          <span class="pin__words">
            <span class="pin__word" data-word="rest" data-rest-text>Save for later</span>
            <span class="pin__word pin__word--done" data-word="done" aria-hidden="true" data-done-text>Saved</span>
          </span>
        </button>
      </div>
      <p class="pin__note" data-note>Lemon tart · 35 min</p>
      <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.

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

What it does

On a white background a pill 58 pixels tall with a thin light grey outline and Save for later inside in Archivo; on its top edge, towards the right, a beech clothespin drawn in SVG is clipped at a slant, with its grain, its light edge, the dark notch between the jaws, the coiled steel spring and its soft shadow; below, the small line Lemon tart · 35 min. Under the pointer the outline darkens, the words widen and gain weight, and the pin leans further and parts its jaws a little, as if a thumb were pressing its handles. Pressed, the pill gives and comes back with a bounce; the two arms of the pin swing open round the spring, which tightens a little, a paper tag with a vermilion stripe and the word SAVED slides down between the jaws, and the pin snaps shut with a small overshoot: the whole pin drops a pixel and comes back, the tag swings once and settles. The words swap: Saved arrives wide and light and lands narrow and full, and the line below says In your recipe box. Pressed again, the pin opens, the tag rises and disappears, and the pin closes again on the bare edge.

How it works

A clothespin in SVG

The pin is two mirrored arms, each one path with the handle, the jaw and the notch, filled with a beech gradient and marked by three thin grain lines and a light edge; between them the spring is a rounded steel rectangle with five turns and a highlight. A group widens them one and a half times, so the pin stays a single drawing to move.

One number to open it

The two arms turn round the same point, the centre of the spring, one by plus degrees and the other by minus degrees: GSAP moves a single number, open, and on every step applies it to both arms with svgOrigin and squeezes the spring in width. Opening is open to OPEN in two tenths with power2.out, closing is open to zero in SNAP seconds with back.out(3), which goes past zero and makes the snap.

The tag

The tag is drawn behind the arms: it comes in from 46 units higher and 14 degrees askew, slides down in 0.34 seconds with power3.out while the pin is open, and when the pin closes it swings to minus 2 degrees with elastic.out. Meanwhile the whole pin drops a pixel and springs back.

The words

Archivo has width and weight axes: under the pointer the words go to width 108; Saved comes in wide at 124 and weight 300 and lands at 100 and 520 with back.out, while the small line changes with a fade.

Accessible

It is a real button with aria-pressed, and an aria-live line announces Saved; the keyboard focus ring shows only with the keyboard. While it moves, clicks are ignored. With reduced motion the tag fades in and out and the pin stays still.

Values to change

NameFileDefaultWhat it changes
LABELscript.jsSave for laterThe words at rest.
DONEscript.jsSavedThe words once the tag is pinned.
NOTEscript.jsLemon tart · 35 minThe small line under the pill.
NOTE_DONEscript.jsIn your recipe boxThe small line once the tag is pinned.
OPENscript.js6How many degrees each arm of the pin opens.
SNAPscript.js0.24The seconds of the snap that closes the pin.
TILTscript.js7How far the pin leans on the pill, in degrees.

Try them in the Playground

When to use it

For a button that puts something aside: a recipe saved for later, a product in favourites, an article bookmarked, a listing to keep an eye on.

On phones

The pill narrows to 42 pixels from the edges and the pin stays clipped on the right. A tap pins and lets go of the tag like a click; the hover lean happens only with a mouse.

With reduced motion

The pin stays still, the tag fades in and out and the words swap without bounces.

Pitfalls

  • The arms must turn round the centre of the spring, not their own: svgOrigin gives GSAP one point of the drawing, in viewBox units, the same for both.
  • The snap comes from the back.out of the closing, which takes the arms a little past zero, so the jaws squeeze tighter than needed and open again: without it the pin closes like a slow door.
  • The tag has to be drawn before the arms, so it sits between the jaws and not on top: in the wrong order it looked laid in front of the pin.
  • Drawn as narrow as a real clothespin it read like a stick at that size: the arms are widened one and a half times by a group with scale, and then it reads.

Libraries

GSAP 3.15.0, Archivo (font variabile) 5.3.0.

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

Testing

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