Black hole

Pro

A delete transition that pulls the chosen items into a tiny singularity while the rest of the list bends around it; undo spins them back out.

Live · keyboard ready
Inbox
  • Deleting or archiving a handful of selected rows, cards, or files, with an Undo right after.
  • Emptying a trash, clearing notifications, or dismissing a batch where the removal should feel final.
  • High frequency deletes such as clearing a single chip or tag. Use a quick height collapse instead.
  • Irreversible actions without undo. The spectacle should not stand in for a confirmation.
  • Very long lists that scroll; the effect paints the visible wrapper, so wrap only the scrolling viewport.

Installation

Pro source and install commands unlock with a Pro plan.

example.tsx
import { useLayoutEffect, useRef, useState } from "react";import { BlackHole } from "@/components/arc/black-hole/black-hole";import type { BlackHoleHandle } from "@/components/arc/black-hole/black-hole"; export function Inbox({ initial }: { initial: Mail[] }) {  const hole = useRef<BlackHoleHandle>(null);  const rows = useRef(new Map<string, HTMLLIElement>());  const [items, setItems] = useState(initial);  const [selected, setSelected] = useState<string[]>([]);   async function remove() {    await hole.current?.swallow(selected.map(id => rows.current.get(id)!));    setItems(current => current.filter(item => !selected.includes(item.id)));    setSelected([]);  }   return (    <BlackHole ref={hole}>      <ul>        {items.map(item => (          <li key={item.id} ref={el => { if (el) rows.current.set(item.id, el); }}>{item.subject}</li>        ))}      </ul>    </BlackHole>  );}

API reference

3 parts. The first is the root.

BlackHole

Wraps a list or grid. Its ref exposes `swallow` and `release`, which run a GPU transition: the chosen items spiral into a tiny singularity while the rest of the content is gravitationally lensed around it, then the hole collapses with a soft shockwave.

PropTypeDefaultDescription
childrenRequiredReactNode–The content that holds the items you remove. It is painted to a texture for the length of the effect.
refRef<BlackHoleHandle>–Gives access to `swallow` and `release`.
classNamestring–Extra class on the root.
styleCSSProperties–Inline styles on the root.

BlackHoleHandle

Imperative methods on the ref. Calls queue, so a release requested during a swallow waits its turn.

PropTypeDefaultDescription
swallow(targets: Element[], options?: BlackHoleOptions) => Promise<void>–Pulls the elements in. They stay hidden afterwards; remove them from your data when the promise resolves so the list closes the gap.
release(targets: Element[], options?: BlackHoleOptions) => Promise<void>–The reverse. Call it in a layout effect right after the items are back in the DOM: they are hidden at once, the list makes room, then they spiral out into place.

BlackHoleOptions

PropTypeDefaultDescription
origin{ x: number; y: number }–Where the hole opens, in viewport coordinates. Defaults to the centre of the targets. Pass the delete button's centre to pull items toward it.
durationnumber1.3Length of the effect in seconds. The reverse runs a little faster.
None of its own
The component has no controls. Wire Delete or Backspace to your own selection, as the demo does.
  • The canvas is aria-hidden and only covers the content while the effect runs; the real DOM is back in place the moment it ends.
  • Announce the result yourself (for example "2 conversations deleted" with an Undo button in a live region). The effect is visual only.
  • Pointer events are not blocked on the rest of the page; disable the trigger while a transition runs to avoid double deletes.
  • Items fall along a logarithmic spiral. Points nearer the hole start earlier, so the inner edge falls faster than the outer one and rows stretch into arms.
  • The rest of the content is lensed with the Einstein ring relation, so text near the hole bends and briefly mirrors around it.
  • The hole opens with a slight overshoot, grows a little as it eats, then collapses and sends out one soft shockwave ring. Undo plays the same path backwards.
  • With reduced motion, or without WebGL2, the items fade out in 200ms and fade back in on release.
  • Measures the wrapper at the start of every run, so it works at any width including 320px.
  • The hole and lensing scale with device pixel ratio, capped at 2x.
  • Painting the wrapper to two textures takes a few milliseconds for a typical list; the shaders and context are prepared while the page is idle.
  • Drawing is three passes on the GPU per frame (lensed content, a 5px mesh for the falling items, and the hole), well within a frame at 120Hz.
  • Textures and buffers are released after each run; one WebGL2 context is kept per instance.

Notes for AI

Give your coding assistant the Markdown reference instead of screenshots.

  • Use for destructive or archive actions on a list, grid, or board where a moment of drama is welcome and undo is offered.
  • Keep refs to the item elements (a Map keyed by id works well) and pass the selected ones to `swallow`. Remove the data only after the promise resolves.
  • For undo, restore the data, then call `release` with the re-inserted elements in `useLayoutEffect` so they never flash. Pair the list with height exit and enter animations so gaps close and reopen smoothly.
  • The painter handles backgrounds, borders, radii, text, images and inline SVG icons. Box shadows, gradients, pseudo elements and cross origin images are not painted.

The full library index for assistants is at /llms.txt.