
The slide-to-confirm button
Buttons, Physics, Transitions
NewFree
The fabric of a made-to-order sofa: Linen, Velvet, Wool, Denim and Corduroy, five small swatches cut with pinking shears. Beside them, hanging from an oak clamp on a brass rail, a large swatch of real cloth, simulated point by point, with its weave and the light on its folds: the velvet glows on the flanks of its folds, the corduroy catches the light in bands. Choosing another fabric, the new swatch flips out of the clamp towards you, falls over the old one pushing it back and settles swaying; the pointer or a finger blows on it like a breeze.
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 fabric picker</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/full.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/instrument-sans@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the fabric picker of a made-to-order sofa: five real radio buttons, Linen, Velvet, Wool, Denim and Corduroy,
each a small swatch cut with pinking shears. Beside them a large swatch hangs from an oak clamp on a brass rail,
and it is real cloth: a grid of points moved by Verlet integration, drawn on a canvas triangle by triangle with
the weave of its fabric and the light on its folds (the velvet glows on its flanks, the cord catches the lamp
across its wales). Choosing another fabric, the new swatch flips out of the clamp, falls over the old one and
settles swaying; the pointer, or a finger, blows on it like a breeze -->
<div class="fabric" data-fabric data-autoclic>
<div class="fabric__hang" data-hang>
<canvas class="fabric__cloth" aria-hidden="true" data-cloth></canvas>
</div>
<fieldset class="fabric__pick">
<legend class="sr">Upholstery fabric</legend>
<div class="fabric__head" aria-hidden="true" data-rise>
<span class="fabric__title">Upholstery</span>
<span class="fabric__hint">Free swatches by post</span>
</div>
<div class="fabric__name" aria-hidden="true" data-name></div>
<p class="fabric__detail" aria-hidden="true" data-rise><span data-detail></span></p>
<p class="fabric__spec" aria-hidden="true" data-rise><span data-spec></span><span class="fabric__price" data-price></span></p>
<div class="fabric__chips" data-chips>
<label class="chip" data-chip>
<input class="chip__radio" type="radio" name="fabric" value="linen">
<span class="chip__lift">
<canvas class="chip__cloth" aria-hidden="true"></canvas>
<svg class="chip__stitch" viewBox="0 0 64 64" preserveAspectRatio="none" aria-hidden="true">
<defs><mask id="sew-linen" maskUnits="userSpaceOnUse" x="-4" y="-4" width="72" height="72"><rect class="chip__sew" x="1.5" y="1.5" width="61" height="61" rx="5" pathLength="1"/></mask></defs>
<rect class="chip__seam" x="1.5" y="1.5" width="61" height="61" rx="5" mask="url(#sew-linen)"/>
</svg>
</span>
<span class="chip__name">Linen</span>
<span class="sr" data-about="detail">Washed linen · Oat</span>
<span class="sr" data-about="spec">290 g/m² · 30,000 rubs</span>
<span class="sr" data-about="price">Included</span>
</label>
<label class="chip" data-chip>
<input class="chip__radio" type="radio" name="fabric" value="velvet">
<span class="chip__lift">
<canvas class="chip__cloth" aria-hidden="true"></canvas>
<svg class="chip__stitch" viewBox="0 0 64 64" preserveAspectRatio="none" aria-hidden="true">
<defs><mask id="sew-velvet" maskUnits="userSpaceOnUse" x="-4" y="-4" width="72" height="72"><rect class="chip__sew" x="1.5" y="1.5" width="61" height="61" rx="5" pathLength="1"/></mask></defs>
<rect class="chip__seam" x="1.5" y="1.5" width="61" height="61" rx="5" mask="url(#sew-velvet)"/>
</svg>
</span>
<span class="chip__name">Velvet</span>
<span class="sr" data-about="detail">Cotton velvet · Bottle green</span>
<span class="sr" data-about="spec">420 g/m² · 50,000 rubs</span>
<span class="sr" data-about="price">+ €220</span>
</label>
<label class="chip" data-chip>
<input class="chip__radio" type="radio" name="fabric" value="wool">
<span class="chip__lift">
<canvas class="chip__cloth" aria-hidden="true"></canvas>
<svg class="chip__stitch" viewBox="0 0 64 64" preserveAspectRatio="none" aria-hidden="true">
<defs><mask id="sew-wool" maskUnits="userSpaceOnUse" x="-4" y="-4" width="72" height="72"><rect class="chip__sew" x="1.5" y="1.5" width="61" height="61" rx="5" pathLength="1"/></mask></defs>
<rect class="chip__seam" x="1.5" y="1.5" width="61" height="61" rx="5" mask="url(#sew-wool)"/>
</svg>
</span>
<span class="chip__name">Wool</span>
<span class="sr" data-about="detail">Herringbone wool · Rust</span>
<span class="sr" data-about="spec">380 g/m² · 40,000 rubs</span>
<span class="sr" data-about="price">+ €180</span>
</label>
<label class="chip" data-chip>
<input class="chip__radio" type="radio" name="fabric" value="denim">
<span class="chip__lift">
<canvas class="chip__cloth" aria-hidden="true"></canvas>
<svg class="chip__stitch" viewBox="0 0 64 64" preserveAspectRatio="none" aria-hidden="true">
<defs><mask id="sew-denim" maskUnits="userSpaceOnUse" x="-4" y="-4" width="72" height="72"><rect class="chip__sew" x="1.5" y="1.5" width="61" height="61" rx="5" pathLength="1"/></mask></defs>
<rect class="chip__seam" x="1.5" y="1.5" width="61" height="61" rx="5" mask="url(#sew-denim)"/>
</svg>
</span>
<span class="chip__name">Denim</span>
<span class="sr" data-about="detail">Selvedge denim · Indigo</span>
<span class="sr" data-about="spec">460 g/m² · 60,000 rubs</span>
<span class="sr" data-about="price">+ €120</span>
</label>
<label class="chip" data-chip>
<input class="chip__radio" type="radio" name="fabric" value="cord">
<span class="chip__lift">
<canvas class="chip__cloth" aria-hidden="true"></canvas>
<svg class="chip__stitch" viewBox="0 0 64 64" preserveAspectRatio="none" aria-hidden="true">
<defs><mask id="sew-cord" maskUnits="userSpaceOnUse" x="-4" y="-4" width="72" height="72"><rect class="chip__sew" x="1.5" y="1.5" width="61" height="61" rx="5" pathLength="1"/></mask></defs>
<rect class="chip__seam" x="1.5" y="1.5" width="61" height="61" rx="5" mask="url(#sew-cord)"/>
</svg>
</span>
<span class="chip__name">Corduroy</span>
<span class="sr" data-about="detail">Cotton corduroy · Mustard</span>
<span class="sr" data-about="spec">340 g/m² · 35,000 rubs</span>
<span class="sr" data-about="price">+ €160</span>
</label>
</div>
</fieldset>
</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.
58 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 fabric picker” from Animorio and fit it into my siteThe swatch is a grid of 13 by 17 points in 3D, hung by its first row from the clamp. Between the points there are links along the rows, along the columns and across both diagonals of each cell (so the cloth neither stretches nor shears) and a softer pair two points apart, which resists bending. The rest lengths are measured on a swatch already draped a little, with soft folds that deepen towards the hem: hanging still it is never a flat sheet.
Each point has its position now and the one before: its speed is the difference, held back by the air, and gravity and the blowing are added times the square of the step. Then the links are brought back to their length twelve times a step, in fixed steps of 1/120 of a second (the frame capped at 1/30). Every fabric has its own weight, stiffness and air: velvet is heavy and falls soft, denim is stiff and makes wide folds, linen is light and sways more.
The new swatch is born held out towards you, turned 93 degrees about the line of the clamp, with a small twist and a push downwards: gravity does the rest. The clamp holds the new one 2.2 pixels in front and the old one 2.2 behind; where they meet, point by point, the front one stays in front and the back one gives more, so the new one, swinging, pushes the old one back. The old one is taken away once the new one covers it, still. When everything is nearly still the swatch eases into its rest pose (worked out once for each fabric by letting it hang a few seconds) and the drawing stops.
Each fabric is painted once on a canvas off screen, twice as fine: the weave, the zigzag edges, the label; and a second time its wrong side. Each point is projected in perspective towards an eye a little over the rail. Then every cell is drawn in two triangles, each with its piece of the painting carried by the affine transform from the three flat points to the three points now, clipped a little wider so no seams show; the cells are sorted by depth and a triangle turned over shows the wrong side.
Every frame the normal of each point gives two maps as large as the grid, laid on the painting with smoothing: the shade in multiply and the sheen in screen. Linen and denim have a soft highlight; wool a fuzz on its edges; velvet is dark where it faces you and glows where the fold turns away (1 minus the normal dotted with the view, to the power 2.2); corduroy uses the Kajiya and Kay hair model with the tangent along the wales, so it shines in bands. The shadow on the wall is the swatch's outline shifted by its distance from the wall, drawn four times smaller and enlarged, so it is soft.
The name is set in Fraunces, a variable face: every fabric has its own weight, softness (the SOFT axis) and optical size, and GSAP carries them from one to the other while the letters come in with a bounce. The small swatches are real radios inside their labels: the arrows work, and a screen reader also hears the description, the weight and the price. The seam round the small swatch chosen is a dashed rectangle with a mask whose stroke (pathLength 1) moves on like the needle.
| Name | File | Default | What it changes |
|---|---|---|---|
START | script.js | 1 | The fabric chosen on opening: 0 Linen, 1 Velvet, 2 Wool, 3 Denim, 4 Corduroy. |
BREEZE | script.js | 1 | How hard the pointer blows on the swatch: 0 not at all, 2 a gust. |
DROP | script.js | 1 | How heavy the falling cloth is: more, a quicker drop and a sharper swing. |
AIR | script.js | 1 | How much the air holds the cloth back: less, it sways for longer. |
STIFF | script.js | 1 | How much every fabric resists bending: 0 soft as silk, 2 stiff as canvas. |
SHEEN | script.js | 1 | How much light the velvet and the corduroy catch on their folds. |
WALL | script.js | 30 | How far the swatch hangs from the wall, in pixels: farther, its shadow falls farther off. |
TOUR | script.js | 2 | In the preview's tour, the seconds from one fabric to the next. |
For choosing the fabric of a sofa, an armchair, a headboard or a made-to-measure shirt, where people want to see and nearly touch the cloth before ordering; also for a shop selling fabric by the metre or curtains. The large swatch shows one fabric at a time: with more than six or seven choices a dropdown beside it is better. The prices and figures here are made up: take them from your catalogue.
The swatch sits over the choice, drawn smaller (270 pixels tall instead of 380), and the physics is the same. The five small swatches sit in one row as wide as the screen, at 390 pixels with room to spare. A finger touching the swatch is a puff, and dragging it sideways the breeze follows; scrolling up and down stays free.
The swatch hangs still in its rest pose, the pointer does not blow, the clamp does not rock, the small swatches neither lift nor tilt. Changing fabric the new swatch fades in over the old one, the name and the lines change with a fade and the seam appears whole.
GSAP 3.15.0, Fraunces (font variabile) 5.3.0, Instrument Sans (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 21 KB; the libraries: GSAP 28 KB.
Tested on 17 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.