Magnetic
A generic wrapper that pulls its child toward the pointer once it enters an activation field larger than the child's own box, and springs back to rest the instant the pointer leaves — a fine-pointer affordance with no touch or keyboard equivalent
Available for Svelte 5, React, and Vue 3.
Preview
<script lang="ts">
// Nothing here runs on a timer: the pull is driven entirely by pointer
// position, and under `prefers-reduced-motion: reduce` Magnetic attaches
// no listeners at all — the button simply sits still.
import { Magnetic } from "fancy-ui-svelte";
import { Button } from "fancy-ui-svelte";
</script>
<div class="flex min-h-[280px] flex-col items-center justify-center gap-4 p-10">
<Magnetic>
<Button variant="outline" size="lg">Get in touch</Button>
</Magnetic>
<p class="text-muted-foreground max-w-sm text-center text-xs">
Move the cursor near the button — it leans toward you before you even touch it, then settles
back once you leave. On a touchscreen nothing moves, and tabbing to it never starts a pull:
attraction needs a pointer position, and a keyboard has none.
</p>
</div>Installation
Usage
<script lang="ts">
// Nothing here runs on a timer: the pull is driven entirely by pointer
// position, and under `prefers-reduced-motion: reduce` Magnetic attaches
// no listeners at all — the button simply sits still.
import { Magnetic } from "fancy-ui-svelte";
import { Button } from "fancy-ui-svelte";
</script>
<div class="flex min-h-[280px] flex-col items-center justify-center gap-4 p-10">
<Magnetic>
<Button variant="outline" size="lg">Get in touch</Button>
</Magnetic>
<p class="text-muted-foreground max-w-sm text-center text-xs">
Move the cursor near the button — it leans toward you before you even touch it, then settles
back once you leave. On a touchscreen nothing moves, and tabbing to it never starts a pull:
attraction needs a pointer position, and a keyboard has none.
</p>
</div>Examples
Basic Usage
A single outlined button that leans toward the cursor.
<script lang="ts">
// Nothing here runs on a timer: the pull is driven entirely by pointer
// position, and under `prefers-reduced-motion: reduce` Magnetic attaches
// no listeners at all — the button simply sits still.
import { Magnetic } from "$lib/fancy-ui/magnetic";
import { Button } from "$lib/fancy-ui/button";
</script>
<div class="flex min-h-[280px] flex-col items-center justify-center gap-4 p-10">
<Magnetic>
<Button variant="outline" size="lg">Get in touch</Button>
</Magnetic>
<p class="text-muted-foreground max-w-sm text-center text-xs">
Move the cursor near the button — it leans toward you before you even touch it, then settles
back once you leave. On a touchscreen nothing moves, and tabbing to it never starts a pull:
attraction needs a pointer position, and a keyboard has none.
</p>
</div>Strength
The same button at three pull strengths, side by side.
<script lang="ts">
import { Magnetic } from "$lib/fancy-ui/magnetic";
import { Button } from "$lib/fancy-ui/button";
</script>
<div class="flex flex-wrap items-center justify-center gap-20 p-10">
<div class="flex flex-col items-center gap-3">
<Magnetic strength={0.15}>
<Button variant="outline">Subtle</Button>
</Magnetic>
<span class="text-muted-foreground text-xs">strength 0.15</span>
</div>
<div class="flex flex-col items-center gap-3">
<Magnetic strength={0.35}>
<Button variant="outline">Default</Button>
</Magnetic>
<span class="text-muted-foreground text-xs">strength 0.35 (default)</span>
</div>
<div class="flex flex-col items-center gap-3">
<Magnetic strength={0.55}>
<Button variant="outline">Strong</Button>
</Magnetic>
<span class="text-muted-foreground text-xs">strength 0.55</span>
</div>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
strength | number | 0.35 | Pull multiplier applied to the pointer's offset from the child's centre. |
radius | number | 40 | Pixels the activation field extends beyond the outer element's own box, on every side. Also sizes the (transparent by default) `::before` halo. |
max | number | 24 | Per-axis clamp (px) on the translated offset — a hard travel cap independent of element geometry, radius, or strength. |
disabled | boolean | false | Disables the pull: no listeners are attached at all, and both translation vars are pinned at `0px`. Never touches the wrapped child's own `disabled` state. |
class | string | - | Additional CSS classes, merged onto the static outer wrapper. |
ref | HTMLDivElement | null | null | Bindable reference to the outer (static, untransformed) wrapper element. |
Slots
| Slot | Description |
|---|---|
children | The single wrapped element — a button, icon link, or card. |
Links
Related components
AnimatedBeam
Fibre-optic connector between two elements: a light packet with a dispersing chromatic tail runs along a glass fibre and blooms behind the destination on arrival; theme-aware, seeded and still under reduced motion.
BorderBeam
A single comet of light riding a container's border: a white-hot head, a long feathered tail fading between two colours, a dim hairline that lights up only within the comet's reach, and a soft bloom that sits on the edge.
Confetti
Confetti celebration effect powered by canvas-confetti with button trigger support