Toggle Morph
A toggle whose knob, as it slides, stretches from 28 to 44 px and becomes a rounded rectangle halfway before turning round again, like a stretching drop, with a bounce, a shift from violet to pink and an updated state label. A real switch as far as screen readers are concerned (a button with the switch role, checked or not), usable from the keyboard; the knob changes sides without stretching when the user reduces motion.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Liquid has 31 effects, including 6 free. Explore the category →
3 usage examples



How it works
The .toggle-morph track is an 80 × 40 px <button role="switch"> (white background at 10%, 2 px border, 20 px corners); the 28 px .toggle-morph-knob sits 4 px from its left edge. On click, the script toggles .active: the knob slides to 44 px with transition: .5s cubic-bezier(.68, -.55, .27, 1.55), which backs it up a little then overshoots its spot, and its gradient shifts from violet to pink; the track takes a violet tint. At the same time, the script restarts the .morphing class, which plays the fx0410-knob animation over 0.5 s: from 30% to 60% of the way, the knob is 44 px wide with 10 px corners, then it becomes a 28 px circle again on arrival.
Two movements overlap: the position, as a transition, and the shape, as an animation. That stretching shape during the trip is what sets the effect apart from an ordinary toggle: the eye sees matter moving rather than a dot changing sides. So that a quick double click restarts the stretch instead of letting it finish, the script removes the class, forces the browser to register it (void knob.offsetWidth), then adds it back. The class is removed when the animation ends (animationend).
The state lives in aria-checked (true or false), which screen readers announce as "on" or "off". The visible label under the track comes from the button's data-on and data-off attributes ("Enabled" / "Disabled"); it is hidden from screen readers, which already have the state. The button's name, aria-label="Toggle", should be replaced with what it controls: "Available for a ride", "Night light". The script walks through every .fx-0410-root .toggle-morph on the page.
Accessibility
- Keyboard: measured in Chromium 151 and Firefox 153, Tab reaches the toggle, Enter or Space flips it, and
aria-checkedfollows. 2 px pink focus ring, 3 px offset, for keyboard users only. In Safari, Tab only stops on buttons if the keyboard setting asks for it (or with Option+Tab). - Screen readers: announced as "Toggle, switch, off" then "on". Give it a name that says what it controls, without the state in it ("Night light", not "Turn on night light").
- Reduced motion: under
prefers-reduced-motion: reduce, the knob loses its position transition and the script does not add.morphing: it changes sides at once, without stretching. Measured in all three engines: no in-between frame for the knob. - Color: the state also reads from the knob's position and from the label, not only from the change to pink. The white label at 50% on a dark background stays above 4.5:1.
Browser compatibility
CSS (transitions, @keyframes) + 1.1 KB of JavaScript, no dependencies. Measured frame by frame in Chromium 151, Firefox 153 and WebKit 26: same path, same halfway stretch, same stop under reduced motion. role="switch" is read by VoiceOver, NVDA and TalkBack; an older reader announces it as a checkbox, with the same state.
Without JavaScript, the toggle stays off and does not flip: if the setting matters, back it with a real checkbox in a form. Without CSS animations, the knob changes sides without stretching.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0410-root">
<div class="toggle-morph-container">
<button type="button" class="toggle-morph" role="switch" aria-checked="false"
aria-label="Toggle" data-on="Enabled" data-off="Disabled">
<span class="toggle-morph-knob"></span>
</button>
<span class="toggle-morph-state" aria-hidden="true">Disabled</span>
</div>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Accessible name (HTML — aria-label) |
"Toggle" | Name what the toggle controls: "Night light", "SMS notifications". Or link it to visible text with aria-labelledby. |
State labels (HTML — data-on / data-off) |
"Enabled" / "Disabled" | The setting's own words: "Open" / "Closed", "Available" / "On break". The script rereads them on every click. Put the starting label in .toggle-morph-state. |
Stretch (CSS — @keyframes fx0410-knob) |
44 px wide with 10 px corners from 30% to 60% of the way | 52 px gives a longer drop; do not exceed the track width minus 8 px. 30%, 60% sets when it stretches: 20%, 70% stretches it earlier and for longer. |
Bounce and duration (CSS — the knob's transition: .5s cubic-bezier(.68, -.55, .27, 1.55), the .5s of .morphing) |
0.5 s, back-up then overshoot | Keep the same duration on both, or the knob stretches after it has arrived. .35s ease gives a snappier toggle, without bounce. |
Colors (CSS — track, knob and .active rules) |
violet knob #a855f7 → #7c3aed, then pink #ec4899 → #a855f7 |
A neutral color at rest and a strong color once on make the state readable at a glance, like the taxi's gray then green. |
FAQ
aria-checked: toggle.addEventListener('click', function () { var on = toggle.getAttribute('aria-checked') === 'true'; /* save on */ });. Put this code after the effect's script, which updates the state first. To start switched on, put class="toggle-morph active", aria-checked="true" and the data-on label in the HTML.role="switch" on a <button> gives both: the button's native keyboard behavior and the "on / off" announcement. If your setting belongs to a form sent all at once, a real styled <input type="checkbox"> remains the best choice.aria-checked already announces "on" or "off". Leaving the label readable too would state it twice, in different words ("Available", then "on"). The label is for the eyes; the spoken state comes from the attribute.