
The inchworm menu
Menus, Hover, Transitions
NewFree
The size select of a merino jumper, made of knitting: the field is the ribbed collar, with the size's woven label sewn on and a ball of yarn at its end. Opening it, the list is knitted downwards row after row, with the yarn running down from the ball; choosing a size, its label flies up into the collar and the list unravels from the bottom, while the yarn, still crinkled from its stitches, goes back on to the ball.
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 knitted select</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/commissioner@5.3.0/index.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/brygada-1918@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the size of a merino jumper, chosen on a select that is knitted. The field is the jumper's ribbed collar, with
the size's woven label sewn on and a ball of the same yarn at its end. Opening, the list is knitted downwards
out of the collar; choosing, the label flies up into the collar and the list unravels back onto the ball -->
<div class="size" data-select data-autoclic>
<span class="size__label" id="size-label" data-label>Size</span>
<div class="size__box" data-box>
<div class="field" role="combobox" tabindex="0" aria-labelledby="size-label" aria-haspopup="listbox" aria-expanded="false" aria-controls="size-list" data-field>
<canvas class="field__rib" aria-hidden="true" data-rib></canvas>
<span class="sr" data-field-sr>M, chest 96 to 101 cm</span>
<span class="tag field__tag" aria-hidden="true" data-field-tag><span class="tag__text" data-tag-text>M</span></span>
<span class="field__text" aria-hidden="true" data-field-slot><span class="field__words" data-field-text>Chest 96–101 cm</span></span>
</div>
<!-- the knitting and the yarn, drawn by the script -->
<canvas class="knit" aria-hidden="true" data-knit></canvas>
<!-- the ball of yarn: the strands turn under a light that stays still -->
<span class="ball" aria-hidden="true" data-ball>
<svg viewBox="-18 -18 36 36">
<defs>
<clipPath id="knit-ball-clip"><circle r="15.2"/></clipPath>
<radialGradient id="knit-ball-light" cx="0.36" cy="0.3" r="0.78">
<stop offset="0" stop-color="#fff" stop-opacity="0.34"/>
<stop offset="0.4" stop-color="#fff" stop-opacity="0"/>
<stop offset="0.7" stop-color="#000" stop-opacity="0.08"/>
<stop offset="1" stop-color="#000" stop-opacity="0.6"/>
</radialGradient>
</defs>
<circle class="ball__core" r="15.2"/>
<g clip-path="url(#knit-ball-clip)">
<g class="ball__turn" data-ball-turn></g>
</g>
<circle r="15.2" fill="url(#knit-ball-light)"/>
<circle class="ball__rim" r="15.2"/>
<path class="ball__tail" d="M8.6 12.4c2.8 2.6 1.4 4.6 3.8 5.6" data-ball-tail/>
</svg>
</span>
<ul class="list" role="listbox" id="size-list" aria-labelledby="size-label" tabindex="-1" data-list>
<li class="opt" role="option" id="size-xs" aria-selected="false" aria-disabled="true" aria-label="XS, chest 86 to 91 cm, sold out" data-say="XS, chest 86 to 91 cm">
<span class="tag" data-tag><span class="tag__text" data-tag-text>XS</span></span>
<span class="opt__text"><span class="opt__chest" data-chest>Chest 86–91 cm</span><span class="opt__note">Sold out</span></span>
<svg class="opt__check" viewBox="0 0 16 16" aria-hidden="true"><path d="M3.2 8.7l3.1 3 6.5-7.2"/></svg>
</li>
<li class="opt" role="option" id="size-s" aria-selected="false" aria-label="S, chest 91 to 96 cm" data-say="S, chest 91 to 96 cm">
<span class="tag" data-tag><span class="tag__text" data-tag-text>S</span></span>
<span class="opt__text"><span class="opt__chest" data-chest>Chest 91–96 cm</span></span>
<svg class="opt__check" viewBox="0 0 16 16" aria-hidden="true"><path d="M3.2 8.7l3.1 3 6.5-7.2"/></svg>
</li>
<li class="opt" role="option" id="size-m" aria-selected="true" aria-label="M, chest 96 to 101 cm" data-say="M, chest 96 to 101 cm">
<span class="tag" data-tag><span class="tag__text" data-tag-text>M</span></span>
<span class="opt__text"><span class="opt__chest" data-chest>Chest 96–101 cm</span></span>
<svg class="opt__check" viewBox="0 0 16 16" aria-hidden="true"><path d="M3.2 8.7l3.1 3 6.5-7.2"/></svg>
</li>
<li class="opt" role="option" id="size-l" aria-selected="false" aria-label="L, chest 101 to 106 cm, only 2 left" data-say="L, chest 101 to 106 cm">
<span class="tag" data-tag><span class="tag__text" data-tag-text>L</span></span>
<span class="opt__text"><span class="opt__chest" data-chest>Chest 101–106 cm</span><span class="opt__note opt__note--low">Only 2 left</span></span>
<svg class="opt__check" viewBox="0 0 16 16" aria-hidden="true"><path d="M3.2 8.7l3.1 3 6.5-7.2"/></svg>
</li>
<li class="opt" role="option" id="size-xl" aria-selected="false" aria-disabled="true" aria-label="XL, chest 106 to 111 cm, sold out" data-say="XL, chest 106 to 111 cm">
<span class="tag" data-tag><span class="tag__text" data-tag-text>XL</span></span>
<span class="opt__text"><span class="opt__chest" data-chest>Chest 106–111 cm</span><span class="opt__note">Sold out</span></span>
<svg class="opt__check" viewBox="0 0 16 16" aria-hidden="true"><path d="M3.2 8.7l3.1 3 6.5-7.2"/></svg>
</li>
<li class="opt" role="option" id="size-xxl" aria-selected="false" aria-label="XXL, chest 111 to 116 cm, made to order in 2 weeks" data-say="XXL, chest 111 to 116 cm">
<span class="tag" data-tag><span class="tag__text" data-tag-text>XXL</span></span>
<span class="opt__text"><span class="opt__chest" data-chest>Chest 111–116 cm</span><span class="opt__note">Made to order, 2 weeks</span></span>
<svg class="opt__check" viewBox="0 0 16 16" aria-hidden="true"><path d="M3.2 8.7l3.1 3 6.5-7.2"/></svg>
</li>
</ul>
</div>
</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>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.
36 values to change
With the Animorio connector, Claude Code takes it from here, guide included, and fits it into your project. Paste this request:
Take “The knitted select” from Animorio and fit it into my siteEvery stitch is a V of two legs of yarn. A leg is a bean tilted about 26 degrees, shaded like a cylinder: at eleven points across it the script works out how much the surface faces the light, which comes from the top left, and turns that into a gradient; then come the dark ends where it goes into the fabric, the diagonal grooves of the twisted plies, a few lighter or darker fibres and two hairs. Fourteen different stitches are drawn once and stamped at random into one strip per row (an offscreen canvas); the ribbed collar is made the same way, with narrower columns and the purl ones sunk between them.
The whole list hangs on one number, the rows knitted. Every frame draws the strips of the finished rows, then the row on the needles cut at the stitch it has reached (forwards on even rows, backwards on odd ones) and at the end the cast-off edge. GSAP moves the number up and down: knitting starts at once and slows into the last row (KNIT), unravelling starts at a steady pull and speeds up (UNRAVEL); opening again while it unravels, the knitting grows again from where it is.
The yarn is a path: from the needles along the edge, round the corner, up the side to the ball, sampled every pixel and a half. Each point is pushed sideways by the crinkles, a sine with an uneven amplitude that travels with the yarn; unravelling, they are big near the edge and smooth out further up, where the yarn is pulled, and the stretch under the edge sags a little. On top go the slanted grooves of the twist, which run with the yarn; the ball turns with the rows and slims down as it unwinds.
On a choice, a copy of the label leaves its row and reaches the collar along a curve, with a sway and a shadow that grows as it lifts; the old one is pulled off before it arrives. The other labels go at once, so the flying one covers nothing; the rows' words instead stay until the edge reaches them, with an opacity that depends on how far away it still is. The measurement on the collar changes in a slot one line high: the old one goes out at the top as the new one comes in from below, always a line apart.
The field is a combobox with aria-expanded, aria-controls and aria-activedescendant, the list a listbox of options with aria-selected, and aria-disabled for the sold-out sizes. Arrows, Home, End, Page Up and Down move the active row, skipping the sold-out ones; Enter, Space and Alt + Up choose, Tab chooses and moves on, Esc closes; letters typed in a row look for the size that starts that way. A mousedown stopped on the list leaves the focus on the field; a press outside, or the focus going away, closes it.
| Name | File | Default | What it changes |
|---|---|---|---|
KNIT | script.js | 0.5 | The seconds the list takes to be knitted, from the collar to its bottom edge. |
UNRAVEL | script.js | 0.75 | The seconds it takes to unravel back on to the ball. |
GAUGE | script.js | [14, 10.5] | The size of a stitch in pixels: its width, and the height of a row. |
CRINKLE | script.js | 3.2 | How wavy the unravelled yarn stays, in pixels: the memory of its stitches. |
LIGHT | script.js | [420, 0.62] | The light on the row under the pointer: its spring's stiffness and damping. |
FLIGHT | script.js | 0.55 | The seconds the chosen label takes to fly up into the collar. |
TYPING | script.js | 0.6 | The seconds a typed search waits for its next letter. |
For choosing an option in a shop of handmade things or textiles: the sizes of a jumper, the yarn, the weight of a blanket. It suits four to eight short options, which all fit in the list without scrolling; with many options (a country, a time zone) a select with a search is needed.
At 390 pixels the field is as wide as the screen minus 32 pixels and the knitting is redrawn to the new size, with stitches of the same size. A tap opens and chooses; without a pointer the light stays on the chosen size and moves to the row just touched.
No knitting, no running yarn and no flying label: the finished list fades in and out, the collar's label and measurement change with a fade, the light goes straight to the row, the ball does not turn and a sold-out size, touched, dims for a moment instead of shaking.
GSAP 3.15.0, Commissioner (font variabile) 5.3.0, Brygada 1918 (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 17 KB; the libraries: GSAP 28 KB.
Tested on 6 October 2026
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.
With your account’s email and password.
or with your email
You’re signed in as .
With your account, you can download the zip of every animation and every pack, for free.