Ballpit
ballpitSimulates gravity, collisions, wall bounce, and pointer repulsion for colorful balls.
Usage
Basic usage
The default parameters are sufficient. Moving the cursor will push away the surrounding balls.
<div className="relative h-64 overflow-hidden rounded-xl"
style={{ background: "oklch(0.14 0.02 255)" }}>
<Ballpit />
</div>Weightless floating
gravity=0 keeps the ball from sinking, bounce=1 is completely elastic and never stops.
<Ballpit gravity={0} bounce={1} count={60} />Big ball small amount
Increase sizeRange and reduce count to create a wallpaper-level sparse ball.
<Ballpit count={14} sizeRange={[20, 36]} gravity={700} />Pure background (without cursor)
followCursor=false Turn off interaction, pure decorative background; light color background is also available.
<Ballpit followCursor={false} count={100} sizeRange={[8, 18]} />When to Use
Use it for a playful, responsive backdrop in a landing-page hero, empty state, or brand play area. Use DotPattern or GridPattern for static regular texture, and Balatro for a painted flow field. Ballpit runs an interactive O(n²) collision simulation, so large counts can reduce frame rate.
Import
import { Ballpit } from "@hulianui/ui"Props
| Name | Type | Default | Description |
|---|---|---|---|
| count | number | 80 | Maximum ball count; the component reduces it for small containers. Collision cost is O(n²), so keep it at or below 200 |
| colors | string[] | chart token ×5 | Ball color matching, circularly allocated according to index; any CSS color string can be passed, the default is light and dark theme |
| gravity | number | 900 | Gravity in px/s²; 0 floats weightlessly, while higher values fall faster |
| bounce | number | 0.86 | Wall/collision energy retention coefficient (0–1); 1 = completely elastic and never stops |
| sizeRange | [number, number] | [10, 26] | Ball radius range [minimum, maximum] (px); also constrained by the short side of the container |
| followCursor | boolean | true | Treat the pointer as a repulsive ball; disable it to remove pointer interaction |
| className | string | — | Class name forwarded to the root, which includes absolute inset-0 z-0 |
| style | CSSProperties | — | Inline styles passed through to the root container |
Slots
| Slot | Type | Description |
|---|---|---|
| fallback | ReactNode | reduced-motion / static bottom when there is no canvas (default is a set of statically arranged small balls) |
Usage Guidelines
- Ball count versus container area:
countis a ceiling. The component reduces it until total ball area is about 42% or less of the container. Forcing many large balls into a narrow card, such ascount=28with radii in[24,44], causes overlap jitter; let the component adapt or lowercountorsizeRange. - O(n²) collision: If the number of balls is too large (>200), real-time collision detection will be stuck. For background scenes, it is recommended to have multiple balls with a small radius instead of large balls.
- Client rendering: Canvas 2D and
requestAnimationFramerun only in the browser. SSR renders the staticfallback; do not mount realtime logic in a server component. - The parent container must be
relative+overflow-hidden.
Related
DotPattern · GridPattern · StripedPattern · Spotlight · RetroGrid · Ripple
Playground
<div className="relative h-64 overflow-hidden rounded-xl"
style={{ background: "oklch(0.14 0.02 255)" }}>
<Ballpit
count={80}
gravity={900}
bounce={0.86}
followCursor={true}
/>
</div>