Buttons
GravityButton
On hover each letter jumps, falls back under gravity and lands with a small bounce, one after another.
Download
- React
- Tailwind CSS v4
- motion
components/ui/gravity-button.tsx
"use client";
import { useState } from "react";
import { motion, useReducedMotion } from "motion/react";
type GravityButtonProps = {
/** Button label. Plain text, because each letter moves on its own. */
children: string;
className?: string;
onClick?: () => void;
/** How high each letter jumps, in pixels. */
height?: number;
/** Seconds between one letter and the next. */
stagger?: number;
};
/* GravityButton: on hover each letter jumps, falls back under gravity and lands with a small bounce, one after another. */
export function GravityButton({
children,
className = "rounded-full bg-[#a78bfa] px-9 py-4 text-[15px] font-medium text-[#1a1530]",
onClick,
height = 10,
stagger = 0.03,
}: GravityButtonProps) {
const reduce = useReducedMotion();
const [hop, setHop] = useState(0);
const jump = () => {
if (!reduce) setHop((h) => h + 1);
};
return (
<button type="button" onClick={onClick} onPointerEnter={jump} onFocus={jump} aria-label={children} className={className}>
<span aria-hidden className="inline-flex">
{[...children].map((ch, i) => (
<motion.span
// A new key per hop restarts the jump.
key={`${hop}-${i}`}
className="inline-block whitespace-pre"
initial={false}
animate={hop ? { y: [0, -height, 0, -height * 0.3, 0] } : undefined}
transition={{ duration: 0.6, times: [0, 0.35, 0.65, 0.82, 1], ease: ["easeOut", "easeIn", "easeOut", "easeIn"], delay: i * stagger }}
>
{ch}
</motion.span>
))}
</span>
</button>
);
}
1. Install the dependency.
npm i motion2. Paste the Code tab into
components/ui/gravity-button.tsx. The styles are Tailwind CSS v4 classes.3. Use it. This is the preview above:
app/gravity-button-demo.tsx
import { GravityButton } from "@/components/ui/gravity-button"; export default function GravityButtonExample() { return ( <div className="grid h-full place-items-center bg-[radial-gradient(circle_at_50%_60%,#1d1a33,#0d0c14_70%)]"> <GravityButton>Drop it in</GravityButton> </div> ); }
Props
| Name | Type | Default | What it does |
|---|---|---|---|
| children | string | — | Button label, plain text. |
| onClick | () => void | — | Click handler. |
| height | number | 10 | How high each letter jumps, in pixels. |
| stagger | number | 0.03 | Seconds between letters. |
| className | string | violet pill | Replaces the default look. |