# MagneticField

> Items are pulled toward a magnet and lean toward it, light ones more than heavy ones, then spring home. The magnet is the pointer or a draggable puck.

- Package: `fancy-ui-react` (React 18/19 + Tailwind CSS v4)
- Category: Effects
- Tags: magnet, pointer, spring, physics, attract, repel, drag, compound
- Types: `MagneticFieldProps`, `MagneticItemProps`, `MagnetPuckProps`, `MagneticPoint`, `MagneticSpring`, `MagneticSpringPreset`, `MagneticFieldSource`, `MagneticFieldContext`
- Docs: https://fancy-ui.rama.app/docs/components/magnetic-field
- Source: https://github.com/RamaHerbin/fancy-ui/tree/main/react/src/components/magnetic-field

## Import

```tsx
import { MagneticField } from 'fancy-ui-react';
```

Setup (once per project): `npm install fancy-ui-react`, then put these lines in your main CSS file (the stylesheet import goes right after `@import "tailwindcss";`):

```css
@import "tailwindcss";
@import "fancy-ui-react/tailwind.css";
```

and import the component styles once in the app entry: `import "fancy-ui-react/styles.css";`. Full guide: https://fancy-ui.rama.app/llms.txt

The names below are the ones to write in React; when one differs from the Svelte name, the Svelte name follows in parentheses. `class` becomes `className`, a callback takes the name the React port declares (`onclick` becomes `onClick`), snippets become `ReactNode` props (render functions when they take arguments) and the default snippet is `children`. Divergences: https://github.com/RamaHerbin/fancy-ui/tree/main/react#divergences-from-the-svelte-api

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `source` | `"pointer" \| "manual"` | `"pointer"` | Where the magnet comes from. `"pointer"` follows a mouse or pen anywhere within `radius` of an item (touch is ignored); `"manual"` reads `position` instead. A `MagnetPuck` placed inside the field drives it in either mode while it is held. |
| `position` | `MagneticPoint \| null` | `null` | The magnet's position in px, relative to the field's padding box, used when `source` is `"manual"`. `null` means no magnet, and the items spring back to rest. |
| `radius` | `number` | `220` | Falloff distance from the item's nearest edge, in px: an item pulls hardest when the magnet touches or overlaps it and stops moving once the magnet is `radius` px away. It can still lean up to twice as far. |
| `strength` | `number` | `1` | Global pull multiplier, applied on top of each item's own `strength`. `0` turns the effect off without detaching anything. |
| `maxTranslate` | `number` | `24` | Largest distance, in px, that any item travels toward the magnet. |
| `maxRotate` | `number` | `6` | Largest lean, in degrees. Items lean toward the magnet's side like a crowd turning its head: clockwise when it is to their left, counter-clockwise when it is to their right. |
| `spring` | `MagneticSpringPreset \| Partial<MagneticSpring>` | `"default"` | How items move and settle: `"soft"`, `"default"` or `"snappy"`, or your own `{ stiffness, damping, mass }` (missing keys come from `"default"`). |
| `polarity` | `1 \| -1` | `1` | `1` attracts and `-1` repels. Only translation flips; the lean stays the same. |
| `disabled` | `boolean` | `false` | Detaches every listener and springs each item back to rest. |
| `onActiveChange` | `(active: boolean) => void` | — | Called when the field wakes up (a magnet is engaged or an item is still settling) and when it goes back to sleep. |
| `className` (Svelte `class`) | `string` | — | Additional CSS classes. |
| `ref` | `HTMLDivElement \| null` | `null` | Element reference (bindable). |
| `MagneticItem.mass` | `number` | `1` | How heavy the item is. Above 1 it moves and leans less and settles more slowly; below 1 it is livelier. |
| `MagneticItem.strength` | `number` | `1` | Per-item pull multiplier, applied on top of the field's `strength`. |
| `MagneticItem.pull` | `boolean` | `true` | Whether the item moves toward the magnet. |
| `MagneticItem.rotate` | `boolean` | `true` | Whether the item leans toward the magnet. |
| `MagneticItem.inline` | `boolean` | `false` | Renders `<span>` wrappers with `display: inline-block` instead of block `<div>`s, for items that sit inside a line of text or an inline row. |
| `MagneticItem.className` (Svelte `MagneticItem.class`) | `string` | — | Additional CSS classes, on the static outer wrapper. |
| `MagneticItem.ref` | `HTMLElement \| null` | `null` | Reference to the static outer wrapper (bindable). |
| `MagnetPuck.position` | `MagneticPoint` | `{ x: 48, y: 48 }` | The puck's centre in px, relative to its positioned parent (the field's padding box when it sits inside a `MagneticField`, where the field must be that parent: no positioned element in between). Bindable; dragging and the arrow keys update it. |
| `MagnetPuck.onPositionChange` | `(position: MagneticPoint) => void` | — | Called with each new position while the puck is dragged or moved with the keyboard. |
| `MagnetPuck.size` | `number` | `40` | Diameter in px. |
| `MagnetPuck.sticky` | `boolean` | `false` | Whether the puck keeps attracting once released. By default it attracts only while held, and the items spring back when it is let go. |
| `MagnetPuck.label` | `string` | `"Magnet"` | Accessible name. A built-in hint naming the keys describes the puck; pass your own `aria-describedby` to replace it. |
| `MagnetPuck.onHeldChange` | `(held: boolean) => void` | — | Called when the puck is picked up (pointer down, or Space/Enter) and put down. |
| `MagnetPuck.className` (Svelte `MagnetPuck.class`) | `string` | — | Additional CSS classes. |
| `MagnetPuck.ref` | `HTMLDivElement \| null` | `null` | Element reference (bindable). |

## Node props

| Prop | Description |
| --- | --- |
| `children` | The content. Wrap each element that should react in a `MagneticItem`. |
| `MagneticItem.children` | The element that reacts. |
| `MagnetPuck.children` | A custom face. Leave it out for the default black disc. |

## Example

No React example is published for this component yet: start from the import above and the props table. The full examples are Svelte-only, on https://fancy-ui.rama.app/docs/components/magnetic-field.md.
