KQ-1582 · MAGPIE/CURSOR
Magpie Cursor
A cursor that collects: point at anything marked data-magpie and its label lifts off the item as a small paper tag, starting at the item's own box and rising toward you, then flies to the head of a string of tags hanging off the cursor in 3D. Each tag follows the one in front on its own spring with the snap character, softer down the string by lag and further back by depth, tilting with its own speed; pointing at a label already held brings it to the front, and past maxTags the oldest lets go and falls away. A click deals the stack out where you stand in a shallow arc that lands on springs.recoil, holds for a beat so you can read it, then gathers up on springs.glide and clears; keyboard focus collects, Enter or Space drops, and Escape gathers the stack away.
Fernworks · Nursery
Point at what you like.
stack empty · point at a plant, click to drop
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/magpie-cursor
Props
- children
- React.ReactNode
- default —
- The area the magpie works in. Mark anything collectable with attribute (default data-magpie).
- depth
- number
- default 22
- How far apart the tags hang in depth, px: 0 is a flat pile, 60 a long string into the page.
- lag
- number
- default 0.5
- How loosely the stack trails the cursor, 0 (tight) to 1 (loose and swinging).
- maxTags
- number
- default 5
- The most tags it carries, 1 to 8; past that the oldest lets go and falls away.
- value / defaultValue
- string[]
- default []
- The collected labels, newest first: controlled, or the labels it starts with.
- onValueChange
- (labels: string[]) => void
- default —
- Fires from the hover, focus, click or key that changed the stack, with the new labels.
- attribute
- string
- default "data-magpie"
- The attribute that marks a collectable item; its value is the label, or the item's text when empty.
- offset
- [number, number]
- default [14, 18]
- Where the stack hangs from the pointer, px.
- spread
- number
- default 11
- How far the ends of a dropped arc tip, degrees.
- hold
- number
- default 1400
- How long a dropped arc rests before it gathers up, ms.
- dropOnClick
- boolean
- default true
- A click drops the stack where you stand.
- followFocus
- boolean
- default true
- Keyboard focus on a tagged item collects it; Enter or Space there drops the stack.
- interactive
- boolean
- default true
- The pointer collects.
- onPick
- (label: string) => void
- default —
- A label was picked up, or brought back to the front.
- onDrop
- (labels: string[]) => void
- default —
- The stack was dropped, with the labels it held, newest first.
- className
- string
- default —
- Classes for the box.
| Prop | Type | Default | Description |
|---|---|---|---|
| children | React.ReactNode | — | The area the magpie works in. Mark anything collectable with attribute (default data-magpie). |
| depth | number | 22 | How far apart the tags hang in depth, px: 0 is a flat pile, 60 a long string into the page. |
| lag | number | 0.5 | How loosely the stack trails the cursor, 0 (tight) to 1 (loose and swinging). |
| maxTags | number | 5 | The most tags it carries, 1 to 8; past that the oldest lets go and falls away. |
| value / defaultValue | string[] | [] | The collected labels, newest first: controlled, or the labels it starts with. |
| onValueChange | (labels: string[]) => void | — | Fires from the hover, focus, click or key that changed the stack, with the new labels. |
| attribute | string | "data-magpie" | The attribute that marks a collectable item; its value is the label, or the item's text when empty. |
| offset | [number, number] | [14, 18] | Where the stack hangs from the pointer, px. |
| spread | number | 11 | How far the ends of a dropped arc tip, degrees. |
| hold | number | 1400 | How long a dropped arc rests before it gathers up, ms. |
| dropOnClick | boolean | true | A click drops the stack where you stand. |
| followFocus | boolean | true | Keyboard focus on a tagged item collects it; Enter or Space there drops the stack. |
| interactive | boolean | true | The pointer collects. |
| onPick | (label: string) => void | — | A label was picked up, or brought back to the front. |
| onDrop | (labels: string[]) => void | — | The stack was dropped, with the labels it held, newest first. |
| className | string | — | Classes for the box. |