Component · magnetic-button
Magnetic button
A button that leans toward the pointer, fills with colour from the exact point you enter and rolls its label. Tuned to the spec in Sujon's own design guide, and just as good by touch.
Vanilla JS2.4 KBTouch pressReduced motionKeyboard focus
Fieldwork Coffee · Concept
Roasted on Tuesdays, delivered by Friday.
Move the pointer near a button: it leans toward you before you touch it. The colour fills from the exact point you enter and drains out where you leave. On a phone, tap: the press and the ripple start under your finger.
Use it
Copy the folder magnetic-button/ (the .js and .css files) into your project. No build step and no framework.
Behaviour
- Pull, label roll and arrow turn run only on a mouse or trackpad (
hover:hover and pointer:fine). - Touch: a short press (scale .96) and a fill ripple from the tap point; nothing depends on hover.
- Keyboard:
:focus-visibleshows the fill and the rolled label, so focus is as visible as hover. prefers-reduced-motion: no movement at all, the fill appears without animation.- The duplicate label is
aria-hidden, so screen readers read the text once. - One rAF spring per button, and it stops when the button is at rest (no idle loop).
Credits
- Spec from Sujon's own Drive guide (Master Award Web Design System, section 6.1: radius pull 0.28, label 0.14, entry-point fill, label slot roll).
- Technique seen on many award sites, for example Lusion and landonorris.com by OFF+BRAND. Written from scratch; no code copied.
<link rel="stylesheet" href="magnetic-button.css">
<a class="mb" href="/contact" data-magnetic>Start a project
<svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
</a>
<button class="mb mb--outline" type="button" data-magnetic data-strength="0.35">Book a call</button>
<script type="module" src="magnetic-button.js"></script>
<!-- or by hand -->
<script type="module">
import { magnetic } from './magnetic-button.js';
const ctl = magnetic(document.querySelector('#cta'), { strength: 0.3, radius: 100 });
// ctl.destroy() removes the listeners
</script>| Option | Default | What it does |
|---|---|---|
| data-strength | 0.28 | How far the button follows the pointer (0 = no pull, fill and roll only). |
| data-radius | 80 | Extra px around the button where the pull starts, so it leans toward you before contact. |
| data-label | button text | Label text when the button has only an icon (it also becomes the rolled label). |
| .mb--outline / --round / --small | Variants. Colours come from --mb-bg, --mb-fg, --mb-fill, --mb-fill-fg. | |
| label (JS) | 0.5 | Label parallax as a share of the button move (the Drive spec: 0.28 button, 0.14 label). |
| stiffness / damping (JS) | 0.14 / 0.72 | Spring feel. Higher stiffness snaps faster; lower damping wobbles more. |