Soda bubbles fizz up through the button while you hover, each on its own size, path and beat, and pop as they reach the top edge. Eight spans on one keyframe, clipped by the button.
Also known as: bubbles hover effect, fizzy button css, rising bubbles animation.
<button class="btn-bubbles"> <span class="btn-bubbles-fizz" aria-hidden="true"><i style="--i:0"></i><i style="--i:1"></i><i style="--i:2"></i><i style="--i:3"></i><i style="--i:4"></i><i style="--i:5"></i><i style="--i:6"></i><i style="--i:7"></i></span> <span class="btn-bubbles-label">Say cheers</span> </button>
.btn-bubbles {
--c: var(--btn-color, #3b82f6);
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5em;
font: 600 var(--btn-size, 18px)/1.2 system-ui, sans-serif;
cursor: pointer;
position: relative;
overflow: hidden;
padding: 0.85em 2em;
border: 0;
border-radius: 0.8em;
background: linear-gradient(to top, color-mix(in srgb, var(--c) 80%, #000), var(--c));
color: #fff;
transition: box-shadow 0.3s ease;}
.btn-bubbles-label { position: relative; }
.btn-bubbles-fizz i {
position: absolute;
bottom: -0.9em;
left: calc(5% + var(--i) * 12%);
width: 0.65em;
height: 0.65em;
border: 1px solid rgba(255, 255, 255, 0.85);
border-radius: 50%;
background: radial-gradient(circle at 35% 30%, rgba(255, 255, 255, 0.9) 0 18%, rgba(255, 255, 255, 0.18) 28%);
opacity: 0;
}
.btn-bubbles-fizz i:nth-child(odd) { width: 0.45em; height: 0.45em; }
.btn-bubbles-fizz i:nth-child(3n) { width: 0.85em; height: 0.85em; }
.btn-bubbles:is(:hover, :focus-visible) { box-shadow: 0 0.4em 1em color-mix(in srgb, var(--c) 35%, transparent); }
.btn-bubbles:is(:hover, :focus-visible) .btn-bubbles-fizz i {
animation: btn-bubbles-rise 1.5s cubic-bezier(0.4, 0, 0.8, 0.6) calc(var(--i) * 0.13s) infinite;
}
.btn-bubbles:is(:hover, :focus-visible) .btn-bubbles-fizz i:nth-child(even) { animation-duration: 1.8s; }
@keyframes btn-bubbles-rise {
0% { opacity: 0; transform: translate(0, 0) scale(0.6); }
15% { opacity: 1; }
45% { transform: translate(0.3em, -1.5em) scale(0.85); }
80% { opacity: 1; }
100% { opacity: 0; transform: translate(-0.15em, -3.8em) scale(1.15); }
}
@media (prefers-reduced-motion: reduce) {
.btn-bubbles, .btn-bubbles *, .btn-bubbles::before, .btn-bubbles::after, .btn-bubbles *::before, .btn-bubbles *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--btn-color on the element or a parent.
Currently previewing #3b82f6.--btn-size. Every other dimension is in
em, so the whole thing scales together. Currently previewing 18px.transition or animation
shorthand. Staggered delays are relative, so they keep their rhythm.