Magnetic button
CSS + JSInside its field the button shifts and leans toward the pointer while its label floats higher, so the two slide apart. JS only reports the pointer position; release it and a springy transition takes it home.
Each checkbox is a small glass cube with the tick on its bottom face. :checked rolls the cube a quarter turn forward; the inputs are real, so keyboard and forms just work.
Your edited version
<input type="checkbox"> stays in the page — keyboard, forms and screen readers all keep working — it is only made invisible with opacity: 0. The <label> next to it is the static hit target, since the pressed element must never be the one that moves.--u, tied to the canvas — the rows, the cubes, the tick and the strike-through — so the list is the same share of a gallery card, the editor and a recording canvas. The numbers below are those units.perspective: 160 units stage so all three read from the same angle.input:checked + label .cube adds one more rotateX(90deg) on top of the resting view angle — a quarter turn forward brings that bottom face round to the front.scaleX(0 → 1) from a fixed-width element instead of animating width, so it costs only a compositor transform.input:checked + labelcube rolls rotateX(90deg)static label = hit targetstrike-through with scaleX<ul class="check-list">
<li>
<input type="checkbox" id="check-0" checked />
<label for="check-0">
<span class="box">
<span class="cube"><i></i><i></i><i></i><i></i><i></i><i><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg></i></span>
</span>
<span class="text">Set the perspective</span>
</label>
</li>
<li>
<input type="checkbox" id="check-1" />
<label for="check-1">
<span class="box">
<span class="cube"><i></i><i></i><i></i><i></i><i></i><i><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg></i></span>
</span>
<span class="text">Preserve the 3D</span>
</label>
</li>
<li>
<input type="checkbox" id="check-2" />
<label for="check-2">
<span class="box">
<span class="cube"><i></i><i></i><i></i><i></i><i></i><i><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg></i></span>
</span>
<span class="text">Flip the cube</span>
</label>
</li>
</ul>
.check-list {
/* one base unit: every length below is a multiple of it, so the list is the same share of a
card, the editor, a full screen and a recording canvas */
--u: 0.37vmin;
display: grid;
gap: calc(7 * var(--u));
/* the row is as wide as its longest line, so the width is what the band binds on. The cube is
30 units and the row padding 12, not 22 and 8: at the old sizes the list came out 86 x 54
vmin, a third short of the box it is allowed to fill */
width: calc(220 * var(--u));
margin: 0;
padding: 0;
list-style: none;
}
.check-list li {
position: relative;
}
/* the real checkbox stays for keyboard/forms/screen readers, only made invisible */
.check-list input {
position: absolute;
inset: 0;
margin: 0;
opacity: 0;
pointer-events: none;
}
/* the label is the static hit target: nothing the pointer can touch ever moves */
.check-list label {
display: flex;
align-items: center;
gap: calc(12 * var(--u));
padding: calc(12 * var(--u)) calc(14 * var(--u));
border: calc(1 * var(--u)) solid #262b4a;
border-radius: calc(12 * var(--u));
/* opaque, so its light words read on either stage: see-through, on the light stage the card was
mid grey and the words on it 2.4:1 */
background: #11142a;
color: #eceefb;
font-size: calc(13 * var(--u));
font-weight: 600;
cursor: pointer;
transition: border-color 0.2s;
}
.check-list label:hover {
border-color: #3a4070;
}
.check-list input:focus-visible + label {
outline: calc(2 * var(--u)) solid #2ee6d6;
outline-offset: calc(2 * var(--u));
}
/* a tiny stage of its own for every box, so all three cubes are seen from the same angle */
.box {
flex: none;
width: calc(30 * var(--u));
height: calc(30 * var(--u));
perspective: calc(160 * var(--u));
pointer-events: none;
}
.cube {
position: relative;
display: block;
width: calc(30 * var(--u));
height: calc(30 * var(--u));
transform-style: preserve-3d;
/* view angle first, then the flip happens around the cube's own X axis */
transform: rotateX(-16deg) rotateY(-24deg) rotateX(0deg);
transition: transform 0.55s cubic-bezier(0.3, 1.5, 0.5, 1);
}
/* turn each face outward, then push it half a side out from the centre */
.cube i:nth-child(1) { transform: rotateY(0deg) translateZ(calc(15 * var(--u))); }
.cube i:nth-child(2) { transform: rotateY(90deg) translateZ(calc(15 * var(--u))); }
.cube i:nth-child(3) { transform: rotateY(180deg) translateZ(calc(15 * var(--u))); }
.cube i:nth-child(4) { transform: rotateY(-90deg) translateZ(calc(15 * var(--u))); }
.cube i:nth-child(5) { transform: rotateX(90deg) translateZ(calc(15 * var(--u))); }
.cube i:nth-child(6) { transform: rotateX(-90deg) translateZ(calc(15 * var(--u))); }
.cube i {
position: absolute;
inset: 0;
border-radius: calc(4 * var(--u));
background: rgb(139 108 255 / 0.22);
/* the edge is an inner line, not a border: squeezed side-on, a 1-unit border breaks up; the
glass's own inner glow is the soft shade that survives */
box-shadow: inset 0 0 0 calc(1 * var(--u)) rgb(139 108 255 / 0.75), inset 0 0 calc(16 * var(--u)) rgb(139 108 255 / 0.3);
}
/* the 6th face is the BOTTOM of the cube: it carries the tick and is out of sight at rest */
.cube i:last-child {
display: grid;
place-items: center;
background: #2ee6d6;
box-shadow: 0 0 calc(16 * var(--u)) rgb(46 230 214 / 0.6);
color: #062b28;
backface-visibility: hidden; /* looking down into the glass cube you'd see the inside otherwise */
}
.cube i:last-child svg {
width: calc(20 * var(--u));
height: calc(20 * var(--u));
}
/* a quarter turn forward brings the bottom face to the front */
.check-list input:checked + label .cube {
transform: rotateX(-16deg) rotateY(-24deg) rotateX(90deg);
}
.text {
position: relative;
transition: opacity 0.3s;
}
/* the strike-through is a line scaled from 0 to 1: transform only, no layout, no repaint */
.text::after {
content: '';
position: absolute;
top: 52%;
right: calc(-3 * var(--u));
left: calc(-3 * var(--u));
height: calc(1.5 * var(--u));
border-radius: calc(1 * var(--u));
background: #2ee6d6;
transform: scaleX(0);
transform-origin: 0 50%;
transition: transform 0.35s ease-out;
}
.check-list input:checked + label .text {
opacity: 0.55;
}
.check-list input:checked + label .text::after {
transform: scaleX(1);
transition-delay: 0.15s;
}