Neu Toggle
Two switches carved into the material: a 64 × 34 px inset track and a large 80 × 40 px model with an icon, whose raised knob slides 40 px with a 3.7 px bounce on each side and turns violet or orange. Real role="switch" buttons, keyboard-operable.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Glassmorphism has 47 effects, including 6 free. Explore the category →
3 usage examples



How it works
Each switch is a <button type="button" role="switch" aria-checked> inside a <label class="neu-toggle-wrap"> with its text: clicking the text also flips the switch, and a screen reader announces “Wi-Fi, switch, on”. The .neu-toggle track (64 × 34 px, border-radius: 17px) is carved by two inset shadows #b8bec7 4px 4px 8px inset and #fff -4px -4px 8px inset; the .neu-toggle-knob (26 px, round) is raised by two 3 px / 6 px drop shadows. When on (.active class), it moves from left: 4px to left: 34px and fills with the #8b5cf6 → #a78bfa gradient. The large .neu-toggle-big (80 × 40 px, 32 px knob with an SVG icon) goes from 4 to 44 px and turns orange #f59e0b → #f97316, icon in white.
The bounce comes from the cubic-bezier(.68, -.55, .27, 1.55) curve over 0.3 s: its two values outside 0–1 make the knob back up before leaving, then overshoot its target. Measured in Chromium on the large model: the knob drops to 0.3 px (3.7 px backwards), rises to 47.7 px (3.7 px past 44) and settles at 44 px — the same round trip when switching off. The JavaScript fits in six lines: one listener per switch flips .active and aria-checked. It covers every .neu-toggle and .neu-toggle-big on the page, with no id: as many switches as you like.
The code holds the effect and nothing else: no site rule or variable (no --primary), no script from another effect, nothing that could collide with your stylesheet. Each switch is a real <button type="button" role="switch"> wrapped by its <label>: Tab reaches it, Space and Enter flip it, so does a click on the label, and the script is a single loop that updates aria-checked. As for the whole family, the surface is declared by .neu-toggle-scene (background: #e0e5ec, padding: 28px, border-radius: 24px): the relief does not depend on your page background.
Accessibility
- Keyboard and screen readers: measured in Chromium and Firefox, Tab reaches both switches, Space and Enter flip them,
aria-checkedfollows (“true” / “false”). The accessible name comes from the wrappinglabel(“Wi-Fi”, “Mode sombre”). Focus is shown byoutline: 2px solid #8b5cf63 px off the edge (3.3:1 on the material, above the required 3:1). - Contrast: the
#6b7280labels on#e0e5ecmeasure 3.8:1 for 13.6 px at weight 600, below the 4.5:1 threshold — switch them to#374151(8.1:1), as in the scenes. The track only exists through its inset shadows (#b8bec7, 1.5:1): when off, the switch is a very pale shape. When on, the violet knob (gradient#8b5cf6→#a78bfa) contrasts from 3.3:1 to 2.2:1 depending on the point of the gradient; the large model's orange#f59e0bonly at 1.7:1 on the material — the state stays readable through the knob's position, not its color. - Position rather than color: the state reads first by which side the knob is on (left off, right on), which does not depend on color vision. For an important setting, add a status text (“2 circuits on”).
- prefers-reduced-motion not in the code: the bounce (3.7 px back-up and overshoot) plays even when the system asks for less motion. It is short and user-triggered; to remove it:
@media (prefers-reduced-motion: reduce) { .neu-toggle-knob, .neu-toggle-big-knob { transition: none } }.
Browser compatibility
CSS: inset and drop box-shadow, transition with a cubic-bezier outside 0–1, linear-gradient, flexbox with gap. JavaScript: querySelectorAll, classList.toggle, setAttribute, no dependency. Measured in Chromium, Firefox and WebKit (Playwright): toggles with the mouse, the keyboard and by clicking the label, identical bounce. Flex gap needs Chrome 84, Firefox 63, Safari 14.1.
Without JavaScript, the switches show their initial state and do not flip: keep the real state server-side, or pair them with a checkbox if the action is critical. Before flex gap, the label touches the switch.
The code
HTML structure to paste into your page (the CSS + JS come with Premium):
<div class="neu-toggle-scene">
<label class="neu-toggle-wrap">
<span class="neu-toggle-label">Wi-Fi</span>
<button type="button" class="neu-toggle active" role="switch" aria-checked="true">
<span class="neu-toggle-knob"></span>
</button>
</label>
<label class="neu-toggle-wrap">
<span class="neu-toggle-label">Dark mode</span>
<button type="button" class="neu-toggle-big" role="switch" aria-checked="false">
<span class="neu-toggle-big-knob"><svg viewBox="0 0 24 24" aria-hidden="true">…</svg></span>
</button>
</label>
</div>
The full HTML + CSS + JS of Neu Toggle, ready to paste — plus all 840 effects. Or $299 once, lifetime access.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Bounce (CSS — transition: .3s cubic-bezier(.68, -.55, .27, 1.55)) |
0.3 s, 3.7 px back-up and overshoot | cubic-bezier(.34, 1.56, .64, 1) keeps the overshoot on arrival without the back-up on departure. ease removes the bounce. Beyond 0.4 s, the switch feels sluggish. |
On color (CSS — .neu-toggle.active .neu-toggle-knob, .neu-toggle-big.active .neu-toggle-big-knob) |
violet #8b5cf6 → #a78bfa; orange #f59e0b → #f97316 | Change the gradient and the colored shadow rgba(…, .4) 0 2px 8px together. “Running” green: #16a34a → #22c55e. |
Large model icon (HTML — <svg> in .neu-toggle-big-knob) |
moon (Dark mode) | Any 24 × 24 stroke SVG, shown at 16 px: gray stroke when off, white when on. Keep aria-hidden="true": the name comes from the label. |
Initial state (HTML — active class + aria-checked) |
Wi-Fi on, Dark mode off | Set both together: class="neu-toggle active" aria-checked="true". To react to a change, listen to click on the button and read aria-checked. |
Material tint (CSS — #e0e5ec, shadows #b8bec7 / #fff) |
light bluish gray | Track, off knob and the .neu-toggle-scene surface must share one tint; change all three with a shadow pair from the same family. |
FAQ
<button role="switch"> announces itself as a switch (“on / off”) and flips with Space as well as Enter. In a form sent to the server, a visually hidden <input type="checkbox" role="switch"> under the track would submit the value on its own; here, read aria-checked in JavaScript.btn.addEventListener('click', () => console.log(btn.getAttribute('aria-checked'))). Listeners run in the order they were added: yours reads the state already flipped.