
The layered button
Buttons, Hover, Physics
Free
The Sign out of an app with a small window in the round end of the pill and a venetian blind in it: slats in real 3D, a warm day behind them, and the light coming through the gaps and falling across the pill in slanting stripes, words included. Under the pointer the slats lean one after the other to aim their stripes at it. At a click the cord with its wooden acorn is pulled, the slats shut from the top in a wave with a bounce and the light goes out: Signed out. Another click opens them from the bottom and the light comes back: Good morning, Rui.
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 venetian blind button</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/mona-sans@5.3.0/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the venetian blind button: the Sign out of an app, with a small window in the round left end of the pill and a
venetian blind in it. Behind the slats there is a warm day; the light comes through the gaps and falls on the
pill in slanting stripes. Under the pointer the slats lean, one after the other, to aim their light at it. A click
pulls the cord: the slats close one by one with a bounce and the light goes. Another click opens them again -->
<div class="blind" data-blind>
<button class="blind__btn" type="button" data-btn data-autoclic>
<span class="blind__fill" aria-hidden="true" data-fill></span>
<span class="blind__under" aria-hidden="true">
<span class="blind__glow" data-glow></span>
<span class="blind__window" data-window>
<span class="blind__sky">
<span class="blind__sun"></span>
<svg class="blind__branch" viewBox="0 0 66 44" data-branch>
<path d="M70 -4C58 4 49 9 40 13c-6 3-12 4-17 8" />
<g>
<path d="M58 3c-5-5-11-4-13 0 4 3 9 3 13 0z" />
<path d="M50 8c-1-7-7-9-11-6 2 4 6 6 11 6z" />
<path d="M46 10c-6 0-9 5-7 9 4-1 6-5 7-9z" />
<path d="M37 14c-4-5-10-5-12-1 3 3 8 4 12 1z" />
<path d="M33 16c-5 2-6 8-3 10 3-3 4-6 3-10z" />
<path d="M26 20c-3-4-8-3-10 0 3 2 7 3 10 0z" />
</g>
</svg>
</span>
<span class="blind__slats" data-slats></span>
<span class="blind__tapes"></span>
<span class="blind__rail"></span>
</span>
</span>
<span class="blind__label">
<span class="blind__word" data-word data-wide="Sign out">Sign out</span>
</span>
<span class="blind__over" aria-hidden="true">
<canvas class="blind__light" data-light></canvas>
<canvas class="blind__cord" data-cord></canvas>
</span>
</button>
<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>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.
29 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 venetian blind button” from Animorio and fit it into my siteThe window has perspective: 200px and overflow: hidden; the slats are spans made by the script (SLATS, from 3 to 10), 1.17 times as tall as the pitch so shut they overlap, turned with rotateX. Each slat has a dark veil (::after) whose opacity follows the square of the sine of its angle: the more the slat turns edge on, the more its face looks away from the room and darkens. The tapes and the head rail sit in front, still. The angle is only written when it changes by a tenth of a degree.
Each slat's angle is a damped spring (2.2 swings a second, damping 0.2) towards its place, plus a coupling to its neighbours, θ'' = K (target − θ) + 160 (θ neighbours − 2θ) − C θ': as with real ladder tapes, a push on one slat runs along the blind as a wave. Below zero degrees a slat hits its neighbour: it stops and takes back BOUNCE times its speed the other way, and that is the bounce of the closing. Steps of 1/240 of a second, the frame capped at 1/30; when nothing moves the loop sleeps.
The stripes are a canvas over the words in mix-blend-mode: screen. For each slat the clear gap is the pitch less the slat's height seen from the front (|cos θ|), and the light leaves at a slope of (90° − θ) · 0.55: an open slat lets it through almost level, a half-shut one sends it down. Each stripe is a trapezoid that widens a little and fades with a gradient, in two passes (a soft edge and a sharp core), and it starts inside the window's opening, with no hard edge at the frame. The warm glow round the window follows the mean of the gaps.
From the pointer's place in the pill the script works out the angle that would send the stripes from the middle of the window at it, and from that the slats' lean, at most LEAN degrees, weighted by the pointer's distance from the pill. They all want the same lean, so the stripes stay parallel and move together; but each reaches it with a delay that grows with its distance from the slat nearest the pointer (rate 16 / (1 + 1.1 · distance)), and that is the wave.
The cord is a verlet chain of six links under a bracket, with gravity and six passes of constraints; the pointer passing through pushes it aside. At a click a hand holds the acorn and pulls it down 9 pixels, lengthening the links, then lets go: the links shorten again and the acorn springs back up swinging. The acorn and its shadow turn along the last link.
The button is a real button, with aria-disabled while it signs out or in and an aria-live region that says Signing out…, Signed out, Signing in… and Good morning, Rui; it is all simulated with GSAP's timing. The words leave first, blurring, and come in after; the pill takes the measured width of the new words (at the width of the wdth axis, so the hover never spills them) with back.out, and when it narrows it waits for the old ones to be gone.
| Name | File | Default | What it changes |
|---|---|---|---|
SIGN_OUT | script.js | Sign out | The words at rest, signed in. |
SIGNING_OUT | script.js | Signing out… | The words while it signs out; the aria-live region says them too. |
SIGNED_OUT | script.js | Signed out | The words once signed out, before Sign in. |
SIGN_IN | script.js | Sign in | The words at rest, signed out. |
SIGNING_IN | script.js | Signing in… | The words while it signs in. |
WELCOME | script.js | Good morning, Rui | The greeting once signed in. The sign in is simulated: in a real site this is the name your server returns. |
WAIT | script.js | 1 | The seconds of the simulated request (at least as long as the slats take to shut). |
SLATS | script.js | 6 | How many slats the blind has. |
OPEN | script.js | 66 | How far the slats turn when open, in degrees: towards 90 they are edge on and more light comes through. |
LEAN | script.js | 22 | How far they lean to aim the light at the pointer, in degrees. 0 they stay still. |
STAGGER | script.js | 0.1 | The seconds between one slat and the next as they shut or open. |
BOUNCE | script.js | 0.45 | How much a slat bounces when it hits the one below: 0 none, 0.8 a lot. |
LIGHT | script.js | 1 | How bright the stripes on the pill are. |
For the sign out or sign in of a home, interiors, hotel and holiday rental, weather or wellbeing app, where daylight belongs. It has to be wired to your real sign out: here the request is simulated.
The pill is between about 200 and 265 pixels wide and always fits at 390; if the words are too long the type gets smaller. A finger resting near the button makes the slats lean towards it, a tap pulls the cord and signs out or in. The window and the cord keep their size.
The slats do not turn or follow the pointer, the cord stays still, the pill does not swell and the words do not widen (under the mouse the fill only lightens a little). At a click the slats fade out and back in already shut, the stripes fade away and the words change with a fade; another click brings them back open, with the light.
GSAP 3.15.0, Mona Sans (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 12 KB; the libraries: GSAP 28 KB.
Tested on 12 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.