Button Ripple Radial Gradient Fill
A press-feedback ripple driven by an animated radial-gradient layer inside an overflow-hidden button, with pointer-tracked origin and a pure CSS centered fallback.
A press-feedback ripple driven by an animated radial-gradient layer inside an overflow-hidden button, with pointer-tracked origin and a pure CSS centered fallback.
Interaction / radial-gradient / button press
Reimplements press-feedback ripples using an animated radial-gradient layer clipped to the button bounds, with pointer-tracked origin, a centered CSS-only fallback, and an ambient hover burst for hero CTAs.
Bloom from click origin
JS reads pointerdown coordinates and drives two CSS custom properties so the gradient center blooms from exactly where the user pressed.
Pure CSS pressed state
A smaller action button fills from its geometric center on :active, avoiding any JS and keeping the effect deterministic across devices.
Emphasis button hover bloom
On hover the background auto-rotates a broader radial burst to call attention to a primary call-to-action while click keeps the tighter press ripple.
A CSS button ripple uses a clipped radial-gradient pseudo-element, expands it with transform on press, and keeps the real button semantics intact.
1<button class="ripple-fill-button" type="button">Save changes</button><style>.ripple-fill-button {7position: relative;overflow: hidden;isolation: isolate;border: 0;border-radius: 999px;padding: .8rem 1.25rem;color: #fff;background: #1e3a5f;font: 700 .95rem/1 ui-sans-serif, system-ui;cursor: pointer;}18.ripple-fill-button::after {content: "";position: absolute;left: 50%;top: 50%;width: 12rem;aspect-ratio: 1;border-radius: 50%;26background: radial-gradient(circle,rgba(125, 211, 252, .58) 0%,rgba(125, 211, 252, .36) 42%,transparent 70%);transform: translate(-50%, -50%) scale(.12);opacity: 0;z-index: -1;pointer-events: none;}35.ripple-fill-button:active::after,.ripple-fill-button:focus-visible::after {animation: button-ripple-recipe-expand .62s ease-out;}.ripple-fill-button:focus-visible {outline: 2px solid #7dd3fc;outline-offset: 4px;}@keyframes button-ripple-recipe-expand {0% { opacity: .9; transform: translate(-50%, -50%) scale(.08); }80% { opacity: .28; transform: translate(-50%, -50%) scale(1); }46100% { opacity: 0; transform: translate(-50%, -50%) scale(1.12); }}@media (prefers-reduced-motion: reduce) {.ripple-fill-button:active::after,.ripple-fill-button:focus-visible::after { animation: none; opacity: .35; transform: translate(-50%, -50%) scale(1); }}</style>
Use a real button element, keep focus-visible styling, and make sure keyboard activation gets feedback too. The ripple layer should be decorative and should not contain the accessible label.
Same expanding radial gradient pseudo-element on the middle button. Without overflow: hidden the ripple bleeds onto the Cancel and Delete neighbours every cycle; with it the wave stays inside the Save button.
The button needs overflow: hidden and a border radius on the clipping element. If the pseudo-element is outside that clipped box, the radial wave will escape into nearby UI.
A shadow spread darkens the whole button edge; a radial layer creates a visible wave from the press area.
One transformed pseudo-element is usually fine. Avoid running many large ripples continuously, animating layout properties, or combining the ripple with heavy blur filters on dense button grids.
Use a real button element, keep focus-visible styling, and make sure keyboard activation gets feedback too. The ripple layer should be decorative and should not contain the accessible label.
Disable the expanding animation and keep a static pressed or focused cue. Ripple is press feedback, but it should not be the only indicator that the button received input.
A button ripple draws a soft circle that expands from the click point outward, fading as it grows. The pattern uses a radial gradient on a pseudo-element with origin coordinates passed in as CSS custom properties from a pointer event, so the ripple actually starts at the cursor — not the button center.
Reach for ripples on Material-leaning button systems and anywhere a click needs tactile confirmation (submit buttons, toolbar actions, list-item taps). Skip it for inline-text links, tiny icon-only buttons, or any control where the ripple radius would extend past the click target itself. Skip it for buttons that fire rapidly (volume +/-, counter steppers) — rapid clicks pile ripples on top of each other and the effect becomes noise.
On pointerdown read the click coordinates, subtract the button’s getBoundingClientRect() origin, and write the results into --ripple-x and --ripple-y CSS custom properties on the button itself. A ::after pseudo-element listens for those variables and renders a radial-gradient(circle at var(--ripple-x) var(--ripple-y), ...) that animates from zero radius to past the longest button diagonal. The pseudo-element sits inside an overflow: hidden button so the ripple clips to the button’s rounded corners. A second class .is-rippling toggles on the same pointerdown to retrigger the keyframe; remove it on animationend so the next click starts clean.
Rapid double-clicks stack two ripples on top of each other; either debounce the trigger to one ripple per animation-duration window or render a queue of pseudo-element clones if you want every click acknowledged. Forgetting overflow: hidden on the button leaks the ripple past the rounded corner and the effect looks amateur. The pseudo-element needs pointer-events: none or it intercepts clicks meant for the button label. Buttons rendered inside a flex/grid container with min-width: 0 can produce zero-width getBoundingClientRect() measurements on the first paint — defer the listener wire-up to requestAnimationFrame after mount.
The ripple is purely decorative — the click event already fires the action; screen readers ignore the pseudo-element. Under prefers-reduced-motion: reduce skip the radius animation entirely and use a flat 60ms opacity fade instead, so the click still has tactile feedback without the outward expansion. Verify the ripple color has at least 3:1 contrast against the button surface or it disappears for low-vision users.
The ripple is press feedback, not the button action itself. Track pointer coordinates into CSS variables for pointer users, but keep the same button state and label for keyboard and screen-reader users.
Use overflow hidden and a bounded radial-gradient so the effect does not bleed outside rounded corners. Reduced motion can switch the ripple to an instant background flash while preserving click acknowledgement.