KQ-1423 · JELLY/CURSOR

Jelly Cursor

Inside its box the pointer becomes a ball of jelly: a soft-body ring of 40 points on damped springs, carried rigidly with the pointer so the cursor stays exact, drawn out into an area-keeping ellipse behind a fast move and set wobbling by a stop, with a damping ratio that `wobble` maps across springs.flick, springs.recoil and looser. Over a button or link it reaches for it side-first and wraps the child's own rounded box as a tint; leaving, the far side clings while the near side follows, until it lets go and snaps back into a ball. Keyboard focus on a child gets the same wrap and Enter or Space squashes it; any key returns the system cursor.

KQ-1423 · JELLY/CURSOR

Waylight · reading app

Read at your own pace.

resting · on start reading

Install

One command — the source lands in your repo. Or copy it from the Code tab.

$ pnpm dlx shadcn@latest add @kinetiq/jelly-cursor

Props

children
React.ReactNode
default —
The area the jelly lives in. Its buttons and links keep every click, focus and key.
size
number
default 28
The jelly ball's diameter, in px.
wobble
number
default 0.5
How long it jiggles once it stops: 0 settles at once (flick), 0.5 bounces twice (recoil), 1 keeps on jiggling.
color
"cobalt" | "mint" | "ember" | "ink" | string
default "cobalt"
Its colour: a named pigment from the theme tokens, or any CSS colour.
stretch
number
default 0.6
How far it draws out behind a fast pointer, 0 to 1.
squash
number
default 0.5
How deep a press squashes it, 0 to 1.
targets
string
default "button, a[href], [role='button'], [data-jelly]"
Which children it wraps itself round, as a CSS selector.
padding
number
default size × 0.22
Room it leaves round a wrapped child, in px; a bigger jelly is puffier (6 at the default size).
stickiness
number
default 0.5
How far it clings to a child you leave before it lets go, 0 to 1.
tint
number
default 0.16
Its body's opacity while wrapped round a child, so the label reads through, 0 to 1.
restOn
string
default "[data-jelly-rest]"
The child it rests on with no pointer inside, as a CSS selector; with none it hides.
hideCursor
boolean
default true
Hide the system cursor while the jelly leads (never over text fields, never after a key press).
followFocus
boolean
default true
Keyboard focus on a child wraps the jelly round it; Enter or Space squashes it.
interactive
boolean
default true
The pointer leads the jelly (mouse and pen; a tap moves it to the tapped child).
onTargetChange
(target: HTMLElement | null) => void
default —
Fires when the child it is wrapped round changes, with that child or null.
className
string
default —
Classes for the box.