Skip to content
animorio
IT
All components

The pocket knife menu

Menus, Physics, SVG, HoverGSAP, CSS onlyAdvancedCompressed weight: Demo 15 KBGSAP 28 KBFreeDownload the zip

The actions menu of a project as a multitool pocket knife: closed, it is the red enamel handle, with its steel liners and the backs of the tools showing edge-on. Pressed, the tools swing out round the pivot one after another, from the middle of the fan outwards, each on its own spring and with the snap of its stop; every tool is an action engraved in the steel, and Delete is the blade. Once a tool is chosen, the others fold away and it makes its own gesture before it snaps shut last.

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 pocket knife menu</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/saira@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <!-- the shared light of the steel: each tool keeps its own copy of these gradients, turned against its own angle,
       so the reflections stay still while the tool turns through them -->
  <svg class="defs" width="0" height="0" aria-hidden="true" focusable="false">
    <defs>
      <linearGradient id="kn-steel" gradientUnits="userSpaceOnUse" x1="0" y1="-190" x2="0" y2="190">
        <stop offset="0" stop-color="#c3c9cf"/>
        <stop offset="0.13" stop-color="#f2f4f6"/>
        <stop offset="0.24" stop-color="#9aa3ab"/>
        <stop offset="0.34" stop-color="#dfe3e7"/>
        <stop offset="0.43" stop-color="#aab2b9"/>
        <stop offset="0.5" stop-color="#f6f8f9"/>
        <stop offset="0.57" stop-color="#959ea7"/>
        <stop offset="0.67" stop-color="#d5dade"/>
        <stop offset="0.79" stop-color="#a2aab2"/>
        <stop offset="0.9" stop-color="#ebeef0"/>
        <stop offset="1" stop-color="#b8bfc6"/>
      </linearGradient>
      <!-- the flat of the blade: a satin finish, a little warmer -->
      <linearGradient id="kn-satin" gradientUnits="userSpaceOnUse" x1="0" y1="-190" x2="0" y2="190">
        <stop offset="0" stop-color="#d6d8d9"/>
        <stop offset="0.16" stop-color="#f6f6f4"/>
        <stop offset="0.3" stop-color="#c3c7ca"/>
        <stop offset="0.45" stop-color="#eeefee"/>
        <stop offset="0.55" stop-color="#c9cdd0"/>
        <stop offset="0.7" stop-color="#f1f2f1"/>
        <stop offset="0.85" stop-color="#bcc1c5"/>
        <stop offset="1" stop-color="#e4e6e6"/>
      </linearGradient>
      <!-- the ground edge of the blade: polished like a mirror, so its reflections are hard -->
      <linearGradient id="kn-mirror" gradientUnits="userSpaceOnUse" x1="0" y1="-190" x2="0" y2="190">
        <stop offset="0" stop-color="#ffffff"/>
        <stop offset="0.1" stop-color="#5e666f"/>
        <stop offset="0.19" stop-color="#f4f6f8"/>
        <stop offset="0.27" stop-color="#3f464e"/>
        <stop offset="0.36" stop-color="#d7dce1"/>
        <stop offset="0.45" stop-color="#ffffff"/>
        <stop offset="0.52" stop-color="#4a525a"/>
        <stop offset="0.61" stop-color="#e8ebee"/>
        <stop offset="0.7" stop-color="#6b737c"/>
        <stop offset="0.8" stop-color="#fbfcfd"/>
        <stop offset="0.9" stop-color="#565e66"/>
        <stop offset="1" stop-color="#eef1f3"/>
      </linearGradient>
      <!-- a reflection crossing the steel: a bright core between two darker bands, so it shows on bright steel too -->
      <linearGradient id="kn-glint" x1="0" y1="0" x2="1" y2="0">
        <stop offset="0" stop-color="#1c232a" stop-opacity="0"/>
        <stop offset="0.2" stop-color="#1c232a" stop-opacity="0.5"/>
        <stop offset="0.36" stop-color="#ffffff" stop-opacity="0.85"/>
        <stop offset="0.5" stop-color="#ffffff" stop-opacity="1"/>
        <stop offset="0.64" stop-color="#ffffff" stop-opacity="0.85"/>
        <stop offset="0.8" stop-color="#1c232a" stop-opacity="0.5"/>
        <stop offset="1" stop-color="#1c232a" stop-opacity="0"/>
      </linearGradient>
      <linearGradient id="kn-brass" gradientUnits="userSpaceOnUse" x1="0" y1="-3.2" x2="0" y2="3.2">
        <stop offset="0" class="br-0"/>
        <stop offset="0.35" class="br-1"/>
        <stop offset="0.75" class="br-2"/>
        <stop offset="1" class="br-3"/>
      </linearGradient>
      <radialGradient id="kn-screw" cx="0.38" cy="0.32" r="0.78">
        <stop offset="0" stop-color="#ffffff"/>
        <stop offset="0.35" stop-color="#d3d8dc"/>
        <stop offset="0.8" stop-color="#737a81"/>
        <stop offset="1" stop-color="#3d4248"/>
      </radialGradient>
    </defs>
  </svg>

  <main class="stage">
    <!-- the pocket knife menu: the actions of a project as the tools of a multitool. Closed, it is the handle, red
         enamel on steel liners, with the backs of the tools showing along its edges. Pressed, the tools flick out
         round the pivot one after another, each on its own back spring, snap into their stops and fan out; each tool
         is an action, engraved with its name. The blade is Delete -->
    <div class="knife" data-knife>
      <div class="knife__body" data-body>
        <button class="handle" type="button" aria-haspopup="menu" aria-expanded="false" aria-controls="knife-menu" data-handle data-autoclic>
          <span class="handle__press">
            <svg class="handle__art" viewBox="0 0 204 60" aria-hidden="true">
              <defs>
                <linearGradient id="kn-liner" x1="0" y1="0" x2="0" y2="1">
                  <stop offset="0" stop-color="#eef1f3"/>
                  <stop offset="0.05" stop-color="#8b939a"/>
                  <stop offset="0.09" stop-color="#4b5258"/>
                  <stop offset="0.5" stop-color="#2c3035"/>
                  <stop offset="0.91" stop-color="#4b5258"/>
                  <stop offset="0.955" stop-color="#a7aeb5"/>
                  <stop offset="1" stop-color="#5d646b"/>
                </linearGradient>
                <linearGradient id="kn-enamel" x1="0" y1="0" x2="0" y2="1">
                  <stop offset="0" class="en-0"/>
                  <stop offset="0.07" class="en-1"/>
                  <stop offset="0.42" class="en-2"/>
                  <stop offset="0.8" class="en-3"/>
                  <stop offset="0.93" class="en-4"/>
                  <stop offset="1" class="en-5"/>
                </linearGradient>
                <radialGradient id="kn-depth" cx="0.5" cy="0.62" r="0.62">
                  <stop offset="0" class="en-glow" stop-opacity="0.4"/>
                  <stop offset="1" class="en-glow" stop-opacity="0"/>
                </radialGradient>
                <linearGradient id="kn-gloss" x1="0" y1="0" x2="0" y2="1">
                  <stop offset="0" stop-color="#ffffff" stop-opacity="0.66"/>
                  <stop offset="0.5" stop-color="#ffffff" stop-opacity="0.16"/>
                  <stop offset="1" stop-color="#ffffff" stop-opacity="0"/>
                </linearGradient>
                <linearGradient id="kn-sweep" x1="0" y1="0" x2="1" y2="0">
                  <stop offset="0" stop-color="#ffffff" stop-opacity="0"/>
                  <stop offset="0.5" stop-color="#ffffff" stop-opacity="0.55"/>
                  <stop offset="1" stop-color="#ffffff" stop-opacity="0"/>
                </linearGradient>
                <radialGradient id="kn-rivet" cx="0.36" cy="0.3" r="0.8">
                  <stop offset="0" stop-color="#ffffff"/>
                  <stop offset="0.3" stop-color="#dfe3e6"/>
                  <stop offset="0.75" stop-color="#7d848b"/>
                  <stop offset="1" stop-color="#454b51"/>
                </radialGradient>
                <clipPath id="kn-scale-clip"><rect x="2" y="5" width="200" height="50" rx="25"/></clipPath>
                <clipPath id="kn-rim-clip"><rect width="204" height="60" rx="30"/></clipPath>
              </defs>
              <!-- the steel liners, and between them the backs of the tools seen edge-on, each with its nail nick -->
              <rect width="204" height="60" rx="30" fill="url(#kn-liner)"/>
              <g class="handle__backs" clip-path="url(#kn-rim-clip)">
                <rect data-back="share" x="40" y="1" width="96" height="1.2" rx="0.6"/>
                <rect data-back="duplicate" x="36" y="2.3" width="116" height="1.1" rx="0.55"/>
                <rect data-back="rename" x="38" y="3.5" width="112" height="1" rx="0.5"/>
                <rect data-back="export" x="36" y="55.5" width="128" height="1.1" rx="0.55"/>
                <rect data-back="delete" x="34" y="56.7" width="134" height="1.3" rx="0.65"/>
              </g>
              <rect x="1.4" y="4.4" width="201.2" height="51.2" rx="25.6" fill="#0b0c0e"/>
              <rect x="2" y="5" width="200" height="50" rx="25" fill="url(#kn-enamel)"/>
              <g clip-path="url(#kn-scale-clip)">
                <ellipse cx="102" cy="38" rx="92" ry="22" fill="url(#kn-depth)"/>
                <rect x="13" y="7.2" width="178" height="15.5" rx="7.75" fill="url(#kn-gloss)"/>
                <rect class="handle__sweep" x="-70" y="0" width="46" height="60" fill="url(#kn-sweep)" data-sweep/>
              </g>
              <rect x="2.5" y="5.5" width="199" height="49" rx="24.5" fill="none" stroke="#ffffff" stroke-opacity="0.16"/>
              <!-- the pivot and the rivet at the far end, set into the enamel -->
              <circle class="en-well" cx="30" cy="30" r="7"/>
              <circle cx="30" cy="30" r="5.8" fill="url(#kn-rivet)"/>
              <circle class="en-well" cx="174" cy="30" r="5.8"/>
              <circle cx="174" cy="30" r="4.7" fill="url(#kn-rivet)"/>
            </svg>
            <!-- the steel plate inlaid in the enamel: it says what the knife is, and what it has just done -->
            <span class="plate" aria-hidden="true" data-plate>
              <span class="plate__face" data-face>
                <svg class="plate__icon" viewBox="0 0 24 24"><rect class="solid" x="9.2" y="15" width="13.6" height="5" rx="2.5"/><path class="solid fan" d="M9.9 15.6 3.3 3.9q-.2-.8.6-.4 6.9 4.6 8.6 11.1z"/></svg>
                <span class="plate__word">Actions</span>
              </span>
            </span>
          </span>
          <span class="sr" data-name>Actions</span>
        </button>

        <!-- the tools: each one turns round the pivot. The menu reads from the top tool to the bottom one -->
        <div class="tools" id="knife-menu" role="menu" aria-label="Project actions" tabindex="-1" data-menu inert>
          <div class="tool-slot" data-slot>
            <button class="tool" type="button" role="menuitem" tabindex="-1" data-tool="share">
              <!-- the awl, with its sewing eye -->
              <svg class="tool__art" viewBox="-16 -24 192 48" aria-hidden="true">
                <defs>
                  <linearGradient id="kn-steel-share" href="#kn-steel" data-light/>
                  <clipPath id="kn-clip-share"><path d="M0 -12H106C124 -12 141 -7 160 -1.3Q162 0 160 1.3C141 7 124 12 106 12H0A12 12 0 0 1 0 -12Z"/></clipPath>
                </defs>
                <path class="tool__ring" d="M0 -12H106C124 -12 141 -7 160 -1.3Q162 0 160 1.3C141 7 124 12 106 12H0A12 12 0 0 1 0 -12Z"/>
                <path class="tool__body" fill="url(#kn-steel-share)" d="M0 -12H106C124 -12 141 -7 160 -1.3Q162 0 160 1.3C141 7 124 12 106 12H0A12 12 0 0 1 0 -12Z"/>
                <g clip-path="url(#kn-clip-share)">
                  <path class="tool__brush" d="M0 -9.5H170M0 -6H170M0 -2.5H170M0 1.5H170M0 5H170M0 8.5H170"/>
                  <path class="tool__facet" d="M106 -12C124 -12 141 -7 160 -1.3L157 -0.4C141 -4.4 124 -6.8 108 -7.2Z"/>
                  <rect class="tool__hole" x="126" y="-1.7" width="13" height="3.4" rx="1.7"/>
                  <path class="tool__lip" d="M127.5 2.2H137.5"/>
                  <path class="tool__nick" d="M40 -8.4Q46.5 -5.4 53 -8.4"/>
                  <path class="tool__lip" d="M40.5 -7.1Q46.5 -4.3 52.5 -7.1"/>
                  <rect class="tool__glint" x="-26" y="-26" width="52" height="52" data-glint/>
                </g>
                <path class="tool__lit" data-lit="top" d="M0 -11.5H106C124 -11.5 141 -6.6 159.5 -1"/>
                <path class="tool__lit" data-lit="bottom" d="M0 11.5H106C124 11.5 141 6.6 159.5 1"/>
                <path class="tool__hit" d="M0 -12H106C124 -12 141 -7 160 -1.3Q162 0 160 1.3C141 7 124 12 106 12H0A12 12 0 0 1 0 -12Z"/>
              </svg>
              <span class="tool__etch" style="--x0: 56; --x1: 124; --cy: 0">
                <svg class="tool__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M8.6 9.8H7.2a1.9 1.9 0 0 0-1.9 1.9v6.4A1.9 1.9 0 0 0 7.2 20h9.6a1.9 1.9 0 0 0 1.9-1.9v-6.4a1.9 1.9 0 0 0-1.9-1.9h-1.4"/><path d="M12 14.6V3.8M8.6 7.2 12 3.8l3.4 3.4"/></svg>
                <span class="tool__label">Share</span>
              </span>
            </button>
          </div>
          <div class="tool-slot" data-slot>
            <button class="tool" type="button" role="menuitem" tabindex="-1" data-tool="duplicate" data-autoclic>
              <!-- the scissors: the upper blade opens on its screw -->
              <svg class="tool__art" viewBox="-16 -24 192 48" aria-hidden="true">
                <defs>
                  <linearGradient id="kn-steel-duplicate" href="#kn-steel" data-light/>
                  <clipPath id="kn-clip-duplicate"><path d="M0 -12H128V12H0A12 12 0 0 1 0 -12Z"/><path d="M124 0.6L164 2.2Q166.4 3.2 164 4.4C153 8 140 12 124 12Z"/></clipPath>
                </defs>
                <path class="tool__ring" d="M0 -12H124C141 -12 154 -6.4 165 0.6Q167 2.4 164 4.4C153 8 140 12 124 12H0A12 12 0 0 1 0 -12Z"/>
                <path class="tool__body" fill="url(#kn-steel-duplicate)" d="M0 -12H128V12H0A12 12 0 0 1 0 -12Z"/>
                <path class="tool__body" fill="url(#kn-steel-duplicate)" d="M124 0.6L164 2.2Q166.4 3.2 164 4.4C153 8 140 12 124 12Z"/>
                <g clip-path="url(#kn-clip-duplicate)">
                  <path class="tool__brush" d="M0 -9.5H170M0 -6H170M0 -2.5H170M0 1.5H170M0 5H170M0 8.5H170"/>
                  <path class="tool__nick" d="M44 -8.4Q50.5 -5.4 57 -8.4"/>
                  <path class="tool__lip" d="M44.5 -7.1Q50.5 -4.3 56.5 -7.1"/>
                  <rect class="tool__glint" x="-26" y="-26" width="52" height="52" data-glint/>
                </g>
                <path class="tool__seam" d="M127.4 -11.4V11.4"/>
                <g data-blade>
                  <path class="tool__body" fill="url(#kn-steel-duplicate)" d="M124 -12C141 -12 154 -6.4 165 0.6Q166.6 1.6 164.4 2.2L124 0.6Z"/>
                  <path class="tool__seam" d="M128 0.7L164 2.2"/>
                  <path class="tool__lit" data-lit="top" d="M124 -11.5C141 -11.5 154 -6 164.6 0.8"/>
                </g>
                <circle class="tool__screw" cx="136" cy="0" r="3.4"/>
                <path class="tool__slot" d="M134.3 -1 137.7 1"/>
                <path class="tool__lit" data-lit="top" d="M0 -11.5H124"/>
                <path class="tool__lit" data-lit="bottom" d="M0 11.5H124C140 11.5 153 7.6 163.6 4"/>
                <path class="tool__hit" d="M0 -12H124C141 -12 154 -6.4 165 0.6Q167 2.4 164 4.4C153 8 140 12 124 12H0A12 12 0 0 1 0 -12Z"/>
              </svg>
              <span class="tool__etch" style="--x0: 52; --x1: 124; --cy: 0">
                <svg class="tool__icon" viewBox="0 0 24 24" aria-hidden="true"><rect x="8.6" y="8.6" width="11.4" height="11.4" rx="2.4"/><path d="M15.4 8.6V6.4A2.4 2.4 0 0 0 13 4H6.4A2.4 2.4 0 0 0 4 6.4V13a2.4 2.4 0 0 0 2.4 2.4h2.2"/></svg>
                <span class="tool__label">Duplicate</span>
              </span>
            </button>
          </div>
          <div class="tool-slot" data-slot>
            <button class="tool" type="button" role="menuitem" tabindex="-1" data-tool="rename">
              <!-- the pen: a knurled barrel and a brass point -->
              <svg class="tool__art" viewBox="-16 -24 192 48" aria-hidden="true">
                <defs>
                  <linearGradient id="kn-steel-rename" href="#kn-steel" data-light/>
                  <clipPath id="kn-clip-rename"><path d="M0 -11H119Q123 -11 124 -8H129L153 -3L160.6 -1.3Q163.4 0 160.6 1.3L153 3L129 8H124Q123 11 119 11H0A11 11 0 0 1 0 -11Z"/></clipPath>
                </defs>
                <path class="tool__ring" d="M0 -11H119Q123 -11 124 -8H129L153 -3L160.6 -1.3Q163.4 0 160.6 1.3L153 3L129 8H124Q123 11 119 11H0A11 11 0 0 1 0 -11Z"/>
                <path class="tool__body" fill="url(#kn-steel-rename)" d="M0 -11H119Q123 -11 124 -8H129L153 -3V3L129 8H124Q123 11 119 11H0A11 11 0 0 1 0 -11Z"/>
                <path class="tool__nib" d="M153 -3L160.6 -1.3Q163.4 0 160.6 1.3L153 3Z"/>
                <g clip-path="url(#kn-clip-rename)">
                  <path class="tool__brush" d="M0 -8.5H170M0 -5H170M0 -1.5H170M0 2H170M0 5.5H170M0 9H170"/>
                  <rect class="tool__collar" x="124" y="-9" width="5" height="18"/>
                  <path class="tool__knurl" d="M132.5 -7.2V7.2M136 -6.5V6.5M139.5 -5.8V5.8M143 -5.1V5.1M146.5 -4.4V4.4"/>
                  <path class="tool__nick" d="M40 -7.6Q46.5 -4.8 53 -7.6"/>
                  <path class="tool__lip" d="M40.5 -6.3Q46.5 -3.7 52.5 -6.3"/>
                  <rect class="tool__glint" x="-26" y="-26" width="52" height="52" data-glint/>
                </g>
                <path class="tool__lit" data-lit="top" d="M0 -10.5H119Q122.6 -10.5 123.6 -8M129 -7.5L153 -2.6"/>
                <path class="tool__lit" data-lit="bottom" d="M0 10.5H119Q122.6 10.5 123.6 8M129 7.5L153 2.6"/>
                <path class="tool__hit" d="M0 -11H119Q123 -11 124 -8H129L153 -3L160.6 -1.3Q163.4 0 160.6 1.3L153 3L129 8H124Q123 11 119 11H0A11 11 0 0 1 0 -11Z"/>
              </svg>
              <span class="tool__etch" style="--x0: 54; --x1: 120; --cy: 0">
                <svg class="tool__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M4.5 19.5h4.2L19.4 8.8a2.1 2.1 0 0 0 0-3l-1.2-1.2a2.1 2.1 0 0 0-3 0L4.5 15.3z"/><path d="M13.6 6.2l4.2 4.2"/></svg>
                <span class="tool__label">Rename</span>
              </span>
            </button>
          </div>
          <div class="tool-slot" data-slot>
            <button class="tool" type="button" role="menuitem" tabindex="-1" data-tool="export">
              <!-- the cap lifter, with its screwdriver end -->
              <svg class="tool__art" viewBox="-16 -24 192 48" aria-hidden="true">
                <defs>
                  <linearGradient id="kn-steel-export" href="#kn-steel" data-light/>
                  <clipPath id="kn-clip-export"><path d="M0 12H160Q164 12 164.6 8.5V2.5Q164.4 0.6 161.6 0.2L148 -1.4Q141.6 -2.2 140.4 -6.2L141.4 -9.4Q141.8 -12 138.6 -12H0A12 12 0 0 0 0 12Z"/></clipPath>
                </defs>
                <path class="tool__ring" d="M0 12H160Q164 12 164.6 8.5V2.5Q164.4 0.6 161.6 0.2L148 -1.4Q141.6 -2.2 140.4 -6.2L141.4 -9.4Q141.8 -12 138.6 -12H0A12 12 0 0 0 0 12Z"/>
                <path class="tool__body" fill="url(#kn-steel-export)" d="M0 12H160Q164 12 164.6 8.5V2.5Q164.4 0.6 161.6 0.2L148 -1.4Q141.6 -2.2 140.4 -6.2L141.4 -9.4Q141.8 -12 138.6 -12H0A12 12 0 0 0 0 12Z"/>
                <g clip-path="url(#kn-clip-export)">
                  <path class="tool__brush" d="M0 -9.5H170M0 -6H170M0 -2.5H170M0 1.5H170M0 5H170M0 8.5H170"/>
                  <path class="tool__facet" d="M155 12H160Q164 12 164.6 8.5V2.5Q164.4 0.6 161.6 0.2L155 -0.6Z"/>
                  <path class="tool__nick" d="M44 8.4Q50.5 5.4 57 8.4"/>
                  <path class="tool__lip" d="M44.5 9.6Q50.5 6.8 56.5 9.6"/>
                  <rect class="tool__glint" x="-26" y="-26" width="52" height="52" data-glint/>
                </g>
                <path class="tool__lip" d="M147.6 -0.8Q142 -1.6 141 -5.6"/>
                <path class="tool__lit" data-lit="top" d="M0 -11.5H138.6Q141.2 -11.5 140.9 -9.4"/>
                <path class="tool__lit" data-lit="bottom" d="M0 11.5H160Q163.6 11.5 164 8.5"/>
                <path class="tool__hit" d="M0 12H160Q164 12 164.6 8.5V2.5Q164.4 0.6 161.6 0.2L148 -1.4Q141.6 -2.2 140.4 -6.2L141.4 -9.4Q141.8 -12 138.6 -12H0A12 12 0 0 0 0 12Z"/>
              </svg>
              <span class="tool__etch" style="--x0: 56; --x1: 132; --cy: 0">
                <svg class="tool__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4v10.6M7.8 10.6 12 14.8l4.2-4.2"/><path d="M4.4 14.6v3.2A2.2 2.2 0 0 0 6.6 20h10.8a2.2 2.2 0 0 0 2.2-2.2v-3.2"/></svg>
                <span class="tool__label">Export</span>
              </span>
            </button>
          </div>
          <div class="tool-slot" data-slot>
            <button class="tool tool--blade" type="button" role="menuitem" tabindex="-1" data-tool="delete">
              <!-- the blade: a satin flat, a mirror ground edge and the name etched in red -->
              <svg class="tool__art" viewBox="-16 -24 192 48" aria-hidden="true">
                <defs>
                  <linearGradient id="kn-satin-delete" href="#kn-satin" data-light/>
                  <linearGradient id="kn-mirror-delete" href="#kn-mirror" data-light/>
                  <clipPath id="kn-clip-delete"><path d="M0 14H122C144 14 160 11 170 4.6Q171.2 3.6 169.4 2.6C156 -6.4 138 -14 114 -14H22Q19 -14 18 -12H0A13 13 0 0 0 0 14Z"/></clipPath>
                </defs>
                <path class="tool__ring" d="M0 14H122C144 14 160 11 170 4.6Q171.2 3.6 169.4 2.6C156 -6.4 138 -14 114 -14H22Q19 -14 18 -12H0A13 13 0 0 0 0 14Z"/>
                <path class="tool__body" fill="url(#kn-satin-delete)" d="M0 14H122C144 14 160 11 170 4.6Q171.2 3.6 169.4 2.6C156 -6.4 138 -14 114 -14H22Q19 -14 18 -12H0A13 13 0 0 0 0 14Z"/>
                <g clip-path="url(#kn-clip-delete)">
                  <path class="tool__brush" d="M0 -2H172M0 2H172M0 6H172M0 10H172"/>
                  <path class="tool__grind" fill="url(#kn-mirror-delete)" d="M22 -14H114C138 -14 156 -6.4 169.4 2.6L167 3.4C154 -1.2 136 -5.6 113 -5.6H25Q23 -5.6 22 -8Z"/>
                  <path class="tool__shoulder" d="M25 -5.6H113C136 -5.6 154 -1.2 167 3.4"/>
                  <path class="tool__nick" d="M40 9.6Q47 6.6 54 9.6"/>
                  <path class="tool__lip" d="M40.5 10.8Q47 8 53.5 10.8"/>
                  <rect class="tool__glint" x="-26" y="-26" width="52" height="52" data-glint/>
                </g>
                <path class="tool__edge" d="M22 -13.6H114C138 -13.6 156 -6 169 2.8"/>
                <path class="tool__run" pathLength="100" d="M22 -13.6H114C138 -13.6 156 -6 169 2.8" data-run/>
                <path class="tool__lit" data-lit="top" d="M22 -13.5H114C138 -13.5 156 -6 169 2.4"/>
                <path class="tool__lit" data-lit="bottom" d="M0 13.5H122C144 13.5 160 10.6 169.5 4.4"/>
                <path class="tool__hit" d="M0 14H122C144 14 160 11 170 4.6Q171.2 3.6 169.4 2.6C156 -6.4 138 -14 114 -14H22Q19 -14 18 -12H0A13 13 0 0 0 0 14Z"/>
              </svg>
              <span class="tool__etch" style="--x0: 52; --x1: 124; --cy: 4.2">
                <svg class="tool__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M4.4 6.9h15.2M9.6 6.9V5.2A1.2 1.2 0 0 1 10.8 4h2.4a1.2 1.2 0 0 1 1.2 1.2v1.7"/><path d="M6.4 6.9 7.2 18.3A1.9 1.9 0 0 0 9.1 20h5.8a1.9 1.9 0 0 0 1.9-1.7l.8-11.4M10.2 10.6v5.6M13.8 10.6v5.6"/></svg>
                <span class="tool__label">Delete</span>
              </span>
            </button>
          </div>
        </div>
      </div>
    </div>
    <p class="sr" aria-live="polite" data-say></p>
  </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.

26 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 pocket knife menu” from Animorio and fit it into my site

What it does

On a black background there is only the closed handle of a pocket knife, 60 pixels high: glossy red enamel with a long highlight along the top, two steel liners round it, the pivot on the left and a rivet on the right, and in the middle an inlaid steel plate with a little knife drawn on it and the word ACTIONS. Along the top edge and the bottom one, between the liners, the backs of the tools show edge-on. Moving over it, a light runs across the enamel and the blade of the little knife on the plate opens a touch. Pressed, the plate says CLOSE and the tools come out turning round the pivot, one after another: first the pen, which swings over the top of the handle, and the cap lifter, which swings under it, then the scissors and the blade, last the awl. Each one starts with the thumb’s flick, slows down, and in the last stretch the spring pulls it against its stop: it hits, bounces a little and settles, the handle takes the knock with a small jolt and a reflection runs down the steel. Open, the tools make a fan pointing left, and the knife slides so that the whole stays in the middle. Each tool is engraved with the name and the icon of its action, read from its point towards the handle: Share on the awl with its eye, Duplicate on the scissors, Rename on the pen with its brass point, Export on the cap lifter with its screwdriver end, and Delete, engraved in red, on the blade, mirror polished along its edge and set a little apart from the others. The steel catches the light by its angle: the reflections stay still while the tools turn, and the edge that faces the lamp lights up. Moving over a tool, it slides out a little, gets brighter, its engraving gets bolder and a reflection runs over it. Choosing it, the others fold back into the handle from the outermost one, the plate says the name of the action and the chosen tool makes its gesture: the awl pokes, the scissors snip twice, the pen writes, the cap lifter pries, the blade cuts with a flash along its edge. Then it snaps shut last, and the plate says for a moment what was done (LINK COPIED, DUPLICATED, RENAMED, EXPORTED, DELETED in red) before it goes back to ACTIONS. From the keyboard the handle opens with Enter, Space or the arrows, the arrows go from tool to tool, which slides out with the focus, an initial goes to the tool that starts with it, Escape and Tab close.

How it works

Tools turning on the pivot

Each tool is a button drawn in SVG with the pivot at its origin, and every frame the script gives it rotate() for its angle and translateX() for how far it slides out. The angles where they stop come from SPREAD: a fan pointing away from the handle, with the blade set further apart. The ones above the middle open swinging over the handle, the ones below swinging under it; they open from the middle outwards and close the other way round, so none of them ever crosses a tool already at rest.

The physics of a slip joint

Each tool has an angle and a speed. It starts with the thumb’s flick (FLICK), the friction of the joint slows it down, a steady force pulls it on, and in the last 34 degrees the spring snaps (SNAP, growing with the square of how close it is). Against the stop it bounces back with BOUNCE of its speed and the spring brings it down again until it settles. It runs by hand on GSAP’s ticker in steps of 1/240 of a second, with the frame’s step capped at 1/30.

The knock and the reflection

At every hit against the stop the handle gets a push on a stiff spring, a turn of a few tenths of a degree round the pivot, and a reflection sets off on the tool: a slanted rectangle with a white core between two dark bands, clipped to the outline, running from the pivot to the point. The smaller knocks of the bounce do not start it again. The knife slides sideways by how open the fan is, to stay in the middle.

A light that stays still

The steel is a linearGradient in userSpaceOnUse that turns against the tool every frame (gradientTransform), so the bands of the reflections stay still in the room and the tool turns through them. The two long edges have a light line that brightens with the dot product of their normal and the direction of the lamp. The shadow sits on a still layer round each tool, so that it always falls the same way.

The gesture and the plate

Once a tool is chosen, the others fold back and it plays its gesture with a GSAP timeline on its own numbers (turn, slide, the opening of the scissors); when the gesture is over it waits until all the others are home and then it shuts. The plate changes its words by rolling: first the old ones leave, then the new ones come in. A word too long for it narrows on the wdth axis of the Saira typeface until it fits.

A real menu

The handle is a button with aria-haspopup, aria-expanded and aria-controls; the tools are menuitems in a role menu, inert while the knife is closed. Up and down arrows, Home, End, the initial, Enter and Space, Escape and Tab which close and give the focus back to the handle. The focus is an amber line following the tool’s outline. Only the outline of the steel, a little widened, takes the pointer, and the result is read out by an aria-live area.

Values to change

NameFileDefaultWhat it changes
FLICKscript.js560The speed of the thumb’s flick that sets a tool going, in degrees per second.
SNAPscript.js5600How hard the spring pulls the tool against its stop in the last stretch, in degrees per second squared.
BOUNCEscript.js0.3How much speed a tool keeps as it bounces off its stop (0 none, 0.6 lively).
STAGGERscript.js0.095The seconds between one tool setting off and the next.
SPREADscript.js22The degrees between one tool and the next in the open fan; the blade keeps a wider gap.
LIFTscript.js6How far a tool slides out under the pointer, in pixels.
HOLDscript.js1.6The seconds the plate says what was done.
OPEN_WORDscript.js'Close'What the plate says while the knife is open.
DONE_WORDSscript.js5What the plate says after each tool, from the top one down.

Try them in the Playground

When to use it

For the actions menu of a file, a project or an item in a list, in a site or an app with an object-like character: a tool for creatives, a file manager, a portfolio, a tool shop. From four to six short actions; with more the fan gets crowded and the words cover each other.

On phones

The knife is 360 units wide and at 390 pixels it fits at almost full scale (about 0.99): the open fan stays inside the screen and the closed handle is as high as on a computer. A tap opens and closes it like a click; a finger pressing a tool makes it slide out and light up like the pointer, and letting go chooses it.

With reduced motion

No turning, no bouncing and no knocks: the knife fades out and comes back open or closed, the tool under the pointer brightens without sliding out, and choosing an action the tool lights up, then the knife fades back closed with the plate saying what was done. The plate’s words fade instead of rolling.

Pitfalls

  • A gradient tied to the tool turned with it and the light changed side at every turn: the steel’s gradients are in userSpaceOnUse and turn against the tool every frame, and the two edges light up by their angle to the lamp.
  • The drop-shadow on the tool turned with it and the shadow fell upwards when the tool pointed left: the shadow sits on a still layer round each tool.
  • Opening the tools in page order, the ones swinging over the handle crossed those already open: now they open from the middle of the fan outwards and close the other way round.
  • The arrival reflection, white on light steel, could not be seen, and the second knock of the bounce restarted it weaker: now it is a white core between two dark bands, and only a stronger knock restarts it.
  • On slow frames the physics, with its step capped at 1/30, slows down while GSAP’s tweens do not: the chosen tool finished its gesture and shut before the others. Now it waits until all the others are home.
  • While the words changed, the old and the new ones overlapped in the plate: now the old ones leave before the new ones come in.
  • The strip of the blade’s back, in the liner, ran past the rounded end of the handle: the strips are clipped to the shape of the liners.
  • The catalogue preview’s click (detail 0) moved the focus and lit the ring: the focus only moves when the keyboard is used.

Libraries

GSAP 3.15.0, Saira (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 15 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