30 CSS Hover Effects 12 / 30
CSS Ripple Button Hover Effect
Five ripple and pulse hover effects on buttons — center-out ring, double ripple, contained wave, border-pulse, and glow-ring expansion — all using pseudo-element scale animations and opacity keyframes to simulate a water-droplet impact.
This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.
The code
<div class="hv-12">
<div class="hv-12__stack">
<div class="hv-12__row">
<button class="hv-12__btn hv-12__btn--ring">Center Ripple</button>
<span class="hv-12__label">scale + opacity keyframe</span>
</div>
<div class="hv-12__row">
<button class="hv-12__btn hv-12__btn--double">Double Ring</button>
<span class="hv-12__label">staggered dual ripple</span>
</div>
<div class="hv-12__row">
<button class="hv-12__btn hv-12__btn--wave">Contained Wave</button>
<span class="hv-12__label">overflow-clipped wave</span>
</div>
<div class="hv-12__row">
<button class="hv-12__btn hv-12__btn--pulse">Border Pulse</button>
<span class="hv-12__label">outline-offset ping</span>
</div>
<div class="hv-12__row">
<button class="hv-12__btn hv-12__btn--glow">Glow Ring</button>
<span class="hv-12__label">box-shadow expand</span>
</div>
</div>
</div> <div class="hv-12">
<div class="hv-12__stack">
<div class="hv-12__row">
<button class="hv-12__btn hv-12__btn--ring">Center Ripple</button>
<span class="hv-12__label">scale + opacity keyframe</span>
</div>
<div class="hv-12__row">
<button class="hv-12__btn hv-12__btn--double">Double Ring</button>
<span class="hv-12__label">staggered dual ripple</span>
</div>
<div class="hv-12__row">
<button class="hv-12__btn hv-12__btn--wave">Contained Wave</button>
<span class="hv-12__label">overflow-clipped wave</span>
</div>
<div class="hv-12__row">
<button class="hv-12__btn hv-12__btn--pulse">Border Pulse</button>
<span class="hv-12__label">outline-offset ping</span>
</div>
<div class="hv-12__row">
<button class="hv-12__btn hv-12__btn--glow">Glow Ring</button>
<span class="hv-12__label">box-shadow expand</span>
</div>
</div>
</div>.hv-12,.hv-12 *,.hv-12 *::before,.hv-12 *::after{box-sizing:border-box;margin:0;padding:0}
.hv-12 ::selection{background:#be185d;color:#fff}
.hv-12{
--bg:#0f0008;
--text:#fce7f3;
--dim:#6b7280;
--pink:#ec4899;
--rose:#f43f5e;
--magenta:#db2777;
--purple:#a855f7;
font-family:'Segoe UI',system-ui,sans-serif;
background:var(--bg);
min-height:100vh;
display:flex;align-items:center;justify-content:center;
padding:60px 24px;
}
.hv-12__stack{
display:flex;flex-direction:column;gap:16px;
width:min(640px,100%);
}
.hv-12__row{
display:flex;align-items:center;justify-content:space-between;
gap:24px;padding:24px 36px;
background:rgba(255,255,255,.025);
border:1px solid rgba(255,255,255,.07);
border-radius:12px;
}
.hv-12__label{font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--dim);white-space:nowrap;flex-shrink:0}
/* shared btn */
.hv-12__btn{
position:relative;
padding:13px 36px;font-size:.95rem;font-weight:600;
letter-spacing:.05em;cursor:pointer;border-radius:8px;border:none;
flex-shrink:0;
}
/* 1 — center ripple */
.hv-12__btn--ring{
background:var(--pink);color:#fff;overflow:visible;
}
.hv-12__btn--ring::after{
content:'';position:absolute;
top:50%;left:50%;
width:100%;height:100%;
border-radius:50%;
background:rgba(255,255,255,.4);
transform:translate(-50%,-50%) scale(0);
pointer-events:none;
}
.hv-12__btn--ring:hover::after{
animation:hv-12-ripple .6s ease-out forwards;
}
@keyframes hv-12-ripple{
0%{transform:translate(-50%,-50%) scale(0);opacity:.7}
100%{transform:translate(-50%,-50%) scale(3.5);opacity:0}
}
/* 2 — double ripple */
.hv-12__btn--double{
background:var(--rose);color:#fff;overflow:visible;
}
.hv-12__btn--double::before{
content:'';position:absolute;
top:50%;left:50%;width:100%;height:100%;
border-radius:50%;background:rgba(255,255,255,.3);
transform:translate(-50%,-50%) scale(0);
pointer-events:none;
}
.hv-12__btn--double::after{
content:'';position:absolute;
top:50%;left:50%;width:100%;height:100%;
border-radius:50%;background:rgba(255,255,255,.2);
transform:translate(-50%,-50%) scale(0);
pointer-events:none;
}
.hv-12__btn--double:hover::before{animation:hv-12-ripple .7s ease-out forwards}
.hv-12__btn--double:hover::after{animation:hv-12-ripple .7s ease-out .25s forwards}
/* 3 — contained wave */
.hv-12__btn--wave{
background:transparent;border:2px solid var(--magenta);color:var(--magenta);
overflow:hidden;
}
.hv-12__btn--wave::before{
content:'';position:absolute;
top:50%;left:50%;
width:0;height:0;
border-radius:50%;
background:rgba(219,39,119,.25);
transform:translate(-50%,-50%);
pointer-events:none;
}
.hv-12__btn--wave:hover::before{
animation:hv-12-wave .6s ease-out forwards;
}
@keyframes hv-12-wave{
0%{width:0;height:0;opacity:.8}
100%{width:300px;height:300px;opacity:0}
}
/* 4 — border pulse */
.hv-12__btn--pulse{
background:var(--purple);color:#fff;
outline:2px solid var(--purple);outline-offset:0;
transition:outline-offset .4s,outline-color .4s;
}
.hv-12__btn--pulse:hover{
outline-offset:10px;
outline-color:transparent;
}
/* 5 — glow ring */
.hv-12__btn--glow{
background:transparent;border:2px solid var(--pink);color:var(--pink);
box-shadow:0 0 0 0 rgba(236,72,153,.4);
transition:box-shadow .4s cubic-bezier(.4,0,.2,1),color .3s,background .3s;
}
.hv-12__btn--glow:hover{
box-shadow:0 0 0 12px rgba(236,72,153,0);
color:#fff;background:var(--pink);
}
@media(max-width:500px){.hv-12__row{flex-direction:column;align-items:flex-start}}
@media(prefers-reduced-motion:reduce){
.hv-12__btn::before,.hv-12__btn::after{animation:none!important}
.hv-12__btn--pulse{transition:none!important}
} .hv-12,.hv-12 *,.hv-12 *::before,.hv-12 *::after{box-sizing:border-box;margin:0;padding:0}
.hv-12 ::selection{background:#be185d;color:#fff}
.hv-12{
--bg:#0f0008;
--text:#fce7f3;
--dim:#6b7280;
--pink:#ec4899;
--rose:#f43f5e;
--magenta:#db2777;
--purple:#a855f7;
font-family:'Segoe UI',system-ui,sans-serif;
background:var(--bg);
min-height:100vh;
display:flex;align-items:center;justify-content:center;
padding:60px 24px;
}
.hv-12__stack{
display:flex;flex-direction:column;gap:16px;
width:min(640px,100%);
}
.hv-12__row{
display:flex;align-items:center;justify-content:space-between;
gap:24px;padding:24px 36px;
background:rgba(255,255,255,.025);
border:1px solid rgba(255,255,255,.07);
border-radius:12px;
}
.hv-12__label{font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--dim);white-space:nowrap;flex-shrink:0}
/* shared btn */
.hv-12__btn{
position:relative;
padding:13px 36px;font-size:.95rem;font-weight:600;
letter-spacing:.05em;cursor:pointer;border-radius:8px;border:none;
flex-shrink:0;
}
/* 1 — center ripple */
.hv-12__btn--ring{
background:var(--pink);color:#fff;overflow:visible;
}
.hv-12__btn--ring::after{
content:'';position:absolute;
top:50%;left:50%;
width:100%;height:100%;
border-radius:50%;
background:rgba(255,255,255,.4);
transform:translate(-50%,-50%) scale(0);
pointer-events:none;
}
.hv-12__btn--ring:hover::after{
animation:hv-12-ripple .6s ease-out forwards;
}
@keyframes hv-12-ripple{
0%{transform:translate(-50%,-50%) scale(0);opacity:.7}
100%{transform:translate(-50%,-50%) scale(3.5);opacity:0}
}
/* 2 — double ripple */
.hv-12__btn--double{
background:var(--rose);color:#fff;overflow:visible;
}
.hv-12__btn--double::before{
content:'';position:absolute;
top:50%;left:50%;width:100%;height:100%;
border-radius:50%;background:rgba(255,255,255,.3);
transform:translate(-50%,-50%) scale(0);
pointer-events:none;
}
.hv-12__btn--double::after{
content:'';position:absolute;
top:50%;left:50%;width:100%;height:100%;
border-radius:50%;background:rgba(255,255,255,.2);
transform:translate(-50%,-50%) scale(0);
pointer-events:none;
}
.hv-12__btn--double:hover::before{animation:hv-12-ripple .7s ease-out forwards}
.hv-12__btn--double:hover::after{animation:hv-12-ripple .7s ease-out .25s forwards}
/* 3 — contained wave */
.hv-12__btn--wave{
background:transparent;border:2px solid var(--magenta);color:var(--magenta);
overflow:hidden;
}
.hv-12__btn--wave::before{
content:'';position:absolute;
top:50%;left:50%;
width:0;height:0;
border-radius:50%;
background:rgba(219,39,119,.25);
transform:translate(-50%,-50%);
pointer-events:none;
}
.hv-12__btn--wave:hover::before{
animation:hv-12-wave .6s ease-out forwards;
}
@keyframes hv-12-wave{
0%{width:0;height:0;opacity:.8}
100%{width:300px;height:300px;opacity:0}
}
/* 4 — border pulse */
.hv-12__btn--pulse{
background:var(--purple);color:#fff;
outline:2px solid var(--purple);outline-offset:0;
transition:outline-offset .4s,outline-color .4s;
}
.hv-12__btn--pulse:hover{
outline-offset:10px;
outline-color:transparent;
}
/* 5 — glow ring */
.hv-12__btn--glow{
background:transparent;border:2px solid var(--pink);color:var(--pink);
box-shadow:0 0 0 0 rgba(236,72,153,.4);
transition:box-shadow .4s cubic-bezier(.4,0,.2,1),color .3s,background .3s;
}
.hv-12__btn--glow:hover{
box-shadow:0 0 0 12px rgba(236,72,153,0);
color:#fff;background:var(--pink);
}
@media(max-width:500px){.hv-12__row{flex-direction:column;align-items:flex-start}}
@media(prefers-reduced-motion:reduce){
.hv-12__btn::before,.hv-12__btn::after{animation:none!important}
.hv-12__btn--pulse{transition:none!important}
}How this works
The classic ripple places an ::after pseudo-element as a circle centered on the button using position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%) scale(0). On hover it scales from 0 to 3–4× the button diameter using transform: scale(4) while fading from opaque to transparent via opacity: 0 — the combination creates the expanding ring effect. A keyframe drives both properties with a single animation call.
The double-ripple variant runs two @keyframes — one on ::before and one on ::after with a .3s delay offset — so a second ring chases the first. The border-pulse variant animates outline from a tight offset (outline-offset: 0) to a wide one (outline-offset: 12px) while fading the outline opacity, creating an outward-expanding halo that feels like a sonar ping.
Customize
- Change ripple color by editing
background: rgba(255,255,255,.5)on the pseudo-element — dark ripples work on light buttons; tryrgba(0,0,0,.2). - Slow the ripple to
.8sfor a calmer, more meditative interaction — faster (.3s) reads as high-energy and reactive. - Make the ring thinner by using
border: 2px solid rgba(255,255,255,.6)instead of a filledbackgroundon the pseudo-element for an outline-only pulse. - Trigger the ripple on
:focus-visibleas well as hover to provide clear keyboard focus feedback using the same visual language. - Combine with a
transform: scale(1.04)on the button itself so the element also lifts slightly as the ripple expands outward.
Watch out for
- The ripple pseudo-element must be a circle — set
border-radius: 50%and equalwidth/heightvalues, not percentage dimensions that react to the button's aspect ratio. - Scaling a large pseudo-element can cause paint-layer promotion — keep
will-changeoff by default and only set it while the animation is running via a class toggle if needed. - On Firefox,
outline-offsettransitions are not always smooth — the border-pulse variant may jump in Firefox; fall back to abox-shadowapproach for consistency.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 60+ | 12+ | 60+ | 60+ |
Scale and opacity animations are compositor-only and universally supported. outline-offset animation has minor Firefox quirks.