Buttons & actions
Magnetic Button
Cursor-aware spring translation physics
detent-magnetic-button
Port status
- Catalog status
- implemented
- Origin
- upstream
- Phase
- 1
- Wave
- phase-1-pilot
- Group
- Buttons & actions
4.9 KB gzip for the class module · inlines the shared spring
Variants
Recorded states
focus-visiblerecorded state; no setterreduced motionrecorded state; no setter
Usage
Import
import '/components/magnetic-button/define.js';
// or register it yourself
import { DetentMagneticButton } from '/components/magnetic-button/magnetic-button.js';
Attributes
disabledformglownamesizestrengthtypevaluevariant
Properties
strength
Slots
default
Events
No events are recorded for this component.
Example
Runnable example: site/examples/magnetic-button.ts. The snippet below is generated from the catalogued attributes.
<detent-magnetic-button
disabled
form=""
glow
name=""
size="md"
strength="0.35"
type="button"
value=""
variant="primary"
></detent-magnetic-button>
Source
The pinned upstream source for this row:
src/components/ui/MagneticButton.tsxsha256 dbce09819e4e…
Native files
The native module and its artifacts:
src/components/magnetic-button/magnetic-button.tssrc/components/magnetic-button/styles.csssrc/components/magnetic-button/define.tsdocs/components/magnetic-button.mdsite/examples/magnetic-button.tstests/browser/magnetic-button.spec.tsevidence/components/magnetic-button.md
API
Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.
Attributes
| Attribute | Property | Type | Default | Notes |
|---|---|---|---|---|
disabled | disabled | boolean | false | Presence semantics: present means true, absent means false. |
form | form | string | '' | Kebab-case attribute; camelCase property. |
glow | glow | boolean | true | Presence semantics: present means true, absent means false. |
name | name | string | '' | Kebab-case attribute; camelCase property. |
size | size | 'sm' | 'md' | 'lg' | 'md' | Kebab-case attribute; camelCase property. |
strength | strength | number | 0.35 | Kebab-case attribute; camelCase property. |
type | type | 'button' | 'submit' | 'reset' | 'button' | Kebab-case attribute; camelCase property. |
value | value | string | '' | Kebab-case attribute; camelCase property. |
variant | variant | 'primary' | 'secondary' | 'outline' | 'ghost' | 'primary' | Kebab-case attribute; camelCase property. |
Properties
| Property | Type | Default | Notes |
|---|---|---|---|
strength | number | 0.35 | Property-only: assign from script; not accepted as an attribute. |
Events
No custom events are recorded for this component.
Slots
| Slot | Notes |
|---|---|
default | Consumer content; rendered through a slot, never re-created. |
CSS customisation
- Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
- React state/hooks replaced by element-owned state with pre-upgrade property preservation.
- Offset is measured from the host box (static) rather than the translated button (moving): settled offset is exactly strength x distance, upstream's was strength x distance / (1 + strength); same visual result at these offsets.
- Touch input never drags the surface (upstream bound mouse handlers only); touch activation itself is engine-limited in Firefox.
- Upstream spread React.ButtonHTMLAttributes onto its button; the native element is form-associated (ElementInternals) and maps type/disabled/name/value/form from those attributes. submit uses a transient hidden native submit button as the submitter so name/value are carried, so event.submitter is that proxy, not the host. Disabled (own or via fieldset) suppresses host.click(), form actions and pointer physics. className is replaced by parts.
- Positional springs use mass 1 (Framer Motion default; upstream states only stiffness 280 / damping 20).
- whileTap scale 0.96 is a CSS :active scale, suppressed under reduced motion.
- Variant palette reads the --detent-color-* theme tokens instead of the upstream fixed dark hex values (#fafafa/#0e0e0e/#1f1f1f/#a1a1a1), which rendered light text on a light page. Dark mode keeps the upstream look; --detent-magnetic-primary-bg/--detent-magnetic-primary-text and --detent-magnetic-glow-color still override per instance.
- glow attribute absence restores the upstream default true (HTML presence semantics); the property glow = false is the opt-out, so glow="false" still means true.
What it does
A real button whose surface is pulled towards the pointer by two positional springs and returns to the origin on leave.
Upstream behaviour
- Spring physics coordinate tracking via Framer Motion
- Configurable pull strength and threshold
- Subtle ambient glow gradient reflection
- Four restrained surface styles: Primary, Secondary, Outline, Ghost
Differences from upstream
- Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
- React state/hooks replaced by element-owned state with pre-upgrade property preservation.
- Offset is measured from the host box (static) rather than the translated button (moving): settled offset is exactly strength x distance, upstream's was strength x distance / (1 + strength); same visual result at these offsets.
- Touch input never drags the surface (upstream bound mouse handlers only); touch activation itself is engine-limited in Firefox.
- Upstream spread React.ButtonHTMLAttributes onto its button; the native element is form-associated (ElementInternals) and maps type/disabled/name/value/form from those attributes. submit uses a transient hidden native submit button as the submitter so name/value are carried, so event.submitter is that proxy, not the host. Disabled (own or via fieldset) suppresses host.click(), form actions and pointer physics. className is replaced by parts.
- Positional springs use mass 1 (Framer Motion default; upstream states only stiffness 280 / damping 20).
- whileTap scale 0.96 is a CSS :active scale, suppressed under reduced motion.
- Variant palette reads the --detent-color-* theme tokens instead of the upstream fixed dark hex values (#fafafa/#0e0e0e/#1f1f1f/#a1a1a1), which rendered light text on a light page. Dark mode keeps the upstream look; --detent-magnetic-primary-bg/--detent-magnetic-primary-text and --detent-magnetic-glow-color still override per instance.
- glow attribute absence restores the upstream default true (HTML presence semantics); the property glow = false is the opt-out, so glow="false" still means true.