Ballpit
An interactive physics ballpit — gravity, walls, and ball-to-ball collisions all simulated live on canvas 2D.
A hand-written physics ballpit on canvas 2D: balls fall under gravity, bounce off the walls, and collide with each other, and moving the mouse pushes the nearby balls aside.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add ballpit.enPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<Ballpit/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
Installation
npx webberui@latest add https://webberui.com/r/ballpit.en.jsonOr, once registries are configured in components.json, install it as @webberui/ballpit.
Usage
The ballpit fills its parent container, so put it inside a bounded box with an explicit height and overflow-hidden:
import { Ballpit } from "@/components/ui/ballpit";
<div className="relative h-[360px] w-full overflow-hidden rounded-xl">
<Ballpit count={48} />
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
count | number | 40 | Number of balls |
minSize | number | 12 | Lower bound of the ball radius (px) |
maxSize | number | 34 | Upper bound of the ball radius (px) |
gravity | number | 900 | Gravitational acceleration (px/s²); set 0 for weightless floating |
restitution | number | 0.82 | Fraction of velocity retained after a collision (0~1) |
colors | string[] | 5 built-in colors | Ball palette, applied by cycling through the list |
interactive | boolean | true | Whether the mouse pushes nearby balls away |
pointerRadius | number | 110 | Radius over which the mouse's push acts (px) |
Accessibility
- When the user has "reduce motion" enabled at the system level, the ballpit only lays the balls out at their starting positions and holds still
- The canvas is a purely decorative layer, marked
aria-hiddenand taking no pointer events, so it interferes with neither the keyboard nor a screen reader - The component is self-contained within its parent's dimensions, scaling with the container through a
ResizeObserverand cleaning up the animation loop on teardown