
The layered button
Buttons, Hover, Physics
Free
The dimmer of a reading lamp, lit by the lamp it dims: a knob of turned metal that has weight, clicks from notch to notch, coasts when flung until a notch catches it and turns the lamp off with a firmer click. As it turns the light fades and warms like a filament, from warm white to the amber of a candle, and with it the metal's reflections, the plate, the words and the shadow change; switched off, only the cool light of a window is left.
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 lamp dimmer</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/jost@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the dimmer of a reading lamp: a fluted metal knob on a dark wall plate, and everything on the plate is seen
by the light of the lamp it dims. The knob clicks from notch to notch, coasts when flung and turns the lamp
off with a firmer click; the light warms as it dims. The plate and the knob are drawn by the script -->
<div class="dimmer" data-dimmer data-autoclic>
<canvas class="dimmer__plate" aria-hidden="true" data-canvas></canvas>
<div class="dimmer__knob" role="slider" tabindex="0" aria-labelledby="dimmer-name" aria-valuemin="0" aria-valuemax="100" data-knob></div>
<div class="dimmer__read">
<p class="dimmer__name" id="dimmer-name" data-name>Reading lamp</p>
<p class="dimmer__warmth" aria-hidden="true" data-kelvin></p>
<p class="dimmer__level" aria-hidden="true" data-level><span data-value></span><span class="dimmer__pct" data-pct>%</span></p>
</div>
<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.
17 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 lamp dimmer” from Animorio and fit it into my siteThe knob has an angle and a speed, and a force that depends on the angle acts on it: −DETENT·sin(2π·(a − a1)/pitch), which pushes towards the nearest notch and has a hump between two notches. The Off notch is wider and nearly twice as deep; past Off and past the top the stops are stiff springs that give a degree or two. The sums are done on GSAP's ticker in steps of 2 milliseconds, with the frame capped at 1/30 of a second, because stiff stops on a slow frame would blow up.
The hand does not set the angle: it pulls the knob with a spring (HAND_K) and damps the difference between its speed and the knob's. Since the notches are stiffer than the hand, the knob lags a few degrees and then clicks into the next notch: you see the click. Let go, the knob keeps its speed, FRICTION slows it down and when it no longer has the strength to get over a hump a notch stops it, with a little bounce. Flung hard downwards it stops at 5%: the Off click is firmer, and the lamp does not go off by mistake.
Held by the rim (past half the radius) the hand's angle is read with atan2 round the middle and the difference between two readings, brought between −180 and 180, is added up; right over the middle the angle means nothing, so it is read but not used. Held from the middle, every pixel up or down turns it by SWEEP/230 degrees. The wheel adds up its moves and asks for a notch every 40 pixels (a trackpad sends many small ones); arrows, Page Up and Down, Home and End put the hand on the right notch and let go once the knob gets there.
The lamp's light follows the notch with a delay: FILAMENT[0] seconds to brighten, FILAMENT[1] to fade. Its temperature goes from WARMTH[0] to WARMTH[1] kelvin with the square root of the level, like the bulbs that warm as they dim; the colour comes from the black body (Tanner Helland's fit) in linear light, divided by the white of a 4000 K bulb, the eye's white. All colours are added in linear light and become pixels through a soft shoulder (1 − e^(−x)) and the gamma; in the dark the eye opens up a little.
The knob is drawn on a canvas at every frame in which something moves. Each of the 48 flutes is a hollow cut into six strips, each with its own normal: its colour is what its reflection sees, the two lights (Phong) and the room. The bevel is a conic gradient of 96 colours worked out the same way. On the turned face the grooves are circles, and the light is worked out along the groove (Kajiya-Kay): each light draws a streak across the middle that does not depend on the knob's angle, so it stays put while the turning marks, a texture on top, go round. Of the slot's two walls, the one facing a light catches it.
The paint is matt: the light of a nearby lamp falls off with the cosine and the square of the distance, and becomes a radial gradient of ten worked-out colours; on top, a satin sheen towards the lamp. The shadows are the knob cast by the two lights (two circles and the band between them), softened with shadowBlur and as dark as the share of light they take away. The printed scale and the words take the light of the spot where they are, and the level's figure goes from a weight of 300 to 560 with the lamp.
| Name | File | Default | What it changes |
|---|---|---|---|
LEVEL | script.js | 70 | The light when the page opens, in percent. |
STEP | script.js | 5 | The percent between two clicks of the knob. |
SWEEP | script.js | 290 | How many degrees the knob turns from Off to full. |
WARMTH | script.js | [1800, 2700] | The colour of the light in kelvin, nearly off and at full: it warms as it dims. |
DETENT | script.js | 6000 | How hard each notch pulls the knob in (0 = no clicks). |
FRICTION | script.js | 14 | How quickly a flung knob slows down. |
FILAMENT | script.js | [0.09, 0.34] | The seconds the bulb takes to brighten and to fade. |
For the app of a connected home or a hotel, a lamp's configurator, the light panel of a room or a stage, and in general for a value that is set by turning and that changes the light.
The plate is 300 pixels wide (less on a narrower screen) and the knob 176. The finger turns it from the rim going round or from the middle up and down, without scrolling the page: touch-action none is only on the knob, outside it the page scrolls. The reflections that follow the mouse are not there on a phone.
The knob goes straight to its notch, with no spring, no bounce and no fling; the reflections do not follow the mouse and there is no entrance. The light only changes with a fade, the filament's.
GSAP 3.15.0, Jost (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 12 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.