Physics Buttons
v1.1.1A suite of four physics-driven action buttons — gooey magnetic (slime), elastic bezier, jelly, and impact — built on GSAP spring physics with style presets and deep prop customization.
Installation
Run once in your project root to configure paths and dependencies:
npx vectorvesper initDownloads and registers the component in your project:
npx vectorvesper add physics-buttonsIf installing manually or managing your package lock:
npm install gsap @gsap/reactUsage
Import
Usage
Props
Four buttons that behave like materials rather than rectangles. One stretches toward your cursor and snaps back, one wobbles like set jelly, one drops and lands with a shower of particles, and one is soft enough to squash. Each ships with variants.
Shared Props
Common to all exported components in this suite.
| Prop | Type | Default | Description |
|---|---|---|---|
children* | React.ReactNode | — | The label. |
onClick | () => void | — | Fires on click, as on a normal button. |
disabled | boolean | false | Disables the button and stops the physics with it. |
type | "button" | "submit" | "reset" | "button" | Native button type. Set it to submit inside a form. |
clickColor | string | "#FFFFFF" | Flash colour on press. |
buttonColor | string | per variant | Fill colour. Each variant carries its own; set this to override. |
aria-label | string | — | Set this when the label is an icon or otherwise not descriptive on its own. |
className | string | "" | Applied to the button. |
style | React.CSSProperties | {} | Inline styles merged onto the button. |
<ElasticButton />
Stretches toward the cursor as it approaches, then snaps back when you leave.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "elastic" | "outlined" | "tension" | "elastic" | Filled, outlined, or a tighter, higher-tension pull. |
width | number | 220 | Width in pixels. |
height | number | 60 | Height in pixels. |
range | number | 140 | How far away the cursor starts pulling on it. |
maxStretch | number | 28 | Furthest it will deform, in pixels. |
pluckForce | number | per variant | How hard it springs back when released. |
borderColor | string | per variant | Edge colour. Mainly for the outlined variant. |
borderWidth | number | per variant | Edge thickness. |
Presets
| Preset | Description | Key Overrides |
|---|---|---|
"elastic" | Solid fill, generous stretch. The default. | — |
"outlined" | Edge only, so the deformation reads as a drawn line. | — |
"tension" | Stiffer — less travel, faster return. | — |
<JellyButton />
Wobbles and settles, like something set rather than solid.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "jelly" | "viscous" | "jelly" | Quick wobble, or a slower, thicker one. |
width | number | 200 | Width in pixels. |
height | number | 56 | Height in pixels. |
range | number | 130 | How far away the cursor starts affecting it. |
radius | number | per variant | Corner radius. |
maxStretch | number | per variant | Furthest it will deform. |
rippleForce | number | per variant | Strength of the wobble that travels through it after a press. |
Presets
| Preset | Description | Key Overrides |
|---|---|---|
"jelly" | Fast wobble that settles quickly. The default. | — |
"viscous" | Thicker and slower — the wobble takes longer to die out. | — |
<ImpactButton />
Falls into place on mount and throws off particles where it lands.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "impact" | "drop" | "sticky" | "impact" | How it arrives and what it does on landing. |
width | number | 200 | Width in pixels. |
height | number | 56 | Height in pixels. |
fallDistance | number | per variant | How far above its resting place it starts. |
impactDelayMs | number | per variant | Pause before the fall begins, in milliseconds. Stagger a row of them with this. |
particleCount | number | per variant | How many particles the landing throws. |
particleColor | string | the button colour | Colour of those particles. |
minRadius | number | per variant | Smallest particle size. |
maxRadius | number | per variant | Largest particle size. |
borderRadius | string | per variant | Corner radius, as any CSS length. |
Presets
| Preset | Description | Key Overrides |
|---|---|---|
"impact" | Lands hard and scatters. The default. | — |
"drop" | A softer arrival with fewer particles. | — |
"sticky" | Lands and clings, deforming on contact. | — |
<SlimeButton />
Soft enough to squash under the cursor, with particles orbiting it.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "slime" | "orbit" | "square" | "slime" | Blobby, orbiting particles, or a squared-off version. |
minWidth | string | "160px" | Minimum width, as any CSS length. It grows to fit its label. |
height | string | "56px" | Height, as any CSS length. |
range | number | 150 | How far away the cursor starts affecting it. |
strength | number | 0.3 | How strongly it reacts within that range. |
squishIntensity | number | -0.35 | How far it flattens on press. Negative squashes; positive would stretch. |
particleCount | number | per variant | How many particles surround it. |
particleColor | string | the button colour | Colour of those particles. |
gravity | number | per variant | Downward pull on the particles. |
haloSpeed | number | per variant | How fast they orbit. |
releaseTimeSec | number | per variant | Seconds to return to rest after release. |
recoilIntensity | number | per variant | How far it overshoots on the way back. |
stdDeviation | number | per variant | Blur that fuses the button and its particles into one blob. Lower separates them. |
borderRadius | string | per variant | Corner radius, as any CSS length. |
Presets
| Preset | Description | Key Overrides |
|---|---|---|
"slime" | Round and gooey, particles fused to the body. The default. | — |
"orbit" | Particles circle at a distance instead of clinging. | — |
"square" | Squared corners, so the squash reads more sharply. | — |
Examples
import { ElasticButton } from "@/components/vv/physics-buttons/ElasticButton";
<ElasticButton onClick={submit}>Send it</ElasticButton>{actions.map((a, i) => (
<ImpactButton key={a.id} impactDelayMs={i * 120} onClick={a.run}>
{a.label}
</ImpactButton>
))}<SlimeButton
variant="orbit"
buttonColor="#7EACB5"
particleColor="#E07B9B"
strength={0.5}
squishIntensity={-0.5}
>
Explore
</SlimeButton>