Hold to delete
HoldPress and hold to delete. Release early to cancel.
The ring answers “how long do I hold?”. Let go early and nothing happens.
by Nurlan Yusifli @yuskraft
A status pill that follows the pointer during a gesture, with feedback right where your eyes already are. One web component, no dependencies, under 4.5 kB gzipped.
Press and hold to delete. Release early to cancel.
The ring answers “how long do I hold?”. Let go early and nothing happens.
Drag files from your desktop
Says what a drop will do before you let go, including when it can’t.
Arrow keys resize. Hold Shift to keep the ratio.
Live values while you drag. Hold ⇧ to keep the ratio.
Drag to the bin to delete, or press Delete.
Armed near the bin, it says “Release to delete”. Anywhere else, letting go is safe.
Drag to select dots. Control or Command A selects all, Escape clears.
A running count of the selection, at the corner you’re dragging.
Turn the dial. It snaps to 15° steps, and tells you when it did.
The one-click case, for contrast: flash() works, but the swatch shows a
check too, because a click deserves feedback on the thing you clicked.
<cursor-status></cursor-status>
<script type="module">
import '@yuskraft/cursor-status/define';
const status = document.querySelector('cursor-status');
status.show('Removing 3 files', { kind: 'progress', progress: 0, dots: true });
status.progress = 0.4; // springs smoothly
status.success('Removed 3 files'); // ring → check, lingers, fades
</script>
A React wrapper and a useCursorStatus() hook, with the tokens mapped to your
theme.
npx shadcn@latest add https://yuskraft.github.io/cursor-status/r/cursor-status.json
Each one is reflected as a property of the same name.
| Attribute | Values | Default | Meaning |
|---|---|---|---|
kind | info progress success error | info | Icon and colour treatment |
label | string | '' | Text shown; changes animate |
progress | 0–1 | 0 | Ring fill for progress; springs |
dots | boolean | false | Animated trailing “…” |
icon | none plus ring check cross | by kind | Override the icon |
open | boolean | false | Visible or not; show() and hide() set it |
follow | pointer manual | pointer | Track the pointer, or position with moveTo() |
scope | CSS selector | document | Only follow and stay visible while the pointer is inside |
placement | bottom-end bottom-start top-end top-start | bottom-end | Side of the cursor; logical, so it flips in RTL |
offset | "x y" px | "18 20" | Gap from the cursor |
linger | ms | 1100 | How long a success stays after the gesture ends |
theme | light dark | system | Force a theme |
motion | reduce | system | Force reduced motion |
stiffness | number | 16 | Follow spring ω |
| Method | Does |
|---|---|
show(label, opts?) | Show, or morph if visible. opts: kind, progress, dots, icon. |
info(label, opts?) | show() with kind: 'info' |
error(label, opts?) | show() with kind: 'error' |
success(label?, opts?) | Ring → check; stays until linger ms after the gesture ends, then fades |
flash(label, opts?) | One-shot: show, succeed, fade |
hide({ delay }?) | Fade out, optionally later |
moveTo(x, y) | Position in client px, for follow="manual" or to anchor at a drop point |
announce(text) | Say something through the polite live region |
| Event | When |
|---|---|
cursor-status:shown | The pill appeared on screen. Bubbles. |
cursor-status:hidden | The pill started to disappear. Bubbles. |
Custom properties on the element. It ships dark and light variants: the light one follows
prefers-color-scheme, or force either with theme="light|dark".
Style the internals with ::part(pill), ::part(icon) and
::part(label).
| Token | Default |
|---|
aria-hidden; state changes and final results go to a polite live region inside the element.follow="manual" with moveTo() to anchor it to a focused control.