Blobs of the button's own color bulge out of its edges on hover and keep swelling, joined by stretchy necks like thick liquid. An inline SVG filter blurs the shapes, then sharpens their alpha.
Also known as: liquid button, goo effect css, metaball button.
<button class="btn-gooey"> <span class="btn-gooey-goo" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i></span> <span class="btn-gooey-label">Get started</span> <svg class="btn-gooey-svg" aria-hidden="true"><filter id="btn-gooey-filter" x="-50%" y="-100%" width="200%" height="300%"><feGaussianBlur in="SourceGraphic" stdDeviation="5"/><feColorMatrix values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -8"/></filter></svg> </button>
.btn-gooey {
--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;
padding: 0.85em 2em;
border: 0;
background: transparent;
color: #fff;}
.btn-gooey-goo {
position: absolute;
inset: 0;
border-radius: 1.5em;
background: var(--c);
filter: url(#btn-gooey-filter);
}
.btn-gooey-goo i {
position: absolute;
width: 1.5em;
height: 1.5em;
border-radius: 50%;
background: var(--c);
transition: translate 0.6s cubic-bezier(0.34, 1.8, 0.64, 1);
}
.btn-gooey-goo i:nth-child(1) { top: 0.1em; left: 12%; }
.btn-gooey-goo i:nth-child(2) { top: 0.2em; left: 52%; width: 1.2em; height: 1.2em; }
.btn-gooey-goo i:nth-child(3) { bottom: 0.1em; left: 28%; }
.btn-gooey-goo i:nth-child(4) { bottom: 0.2em; right: 14%; width: 1.3em; height: 1.3em; }
.btn-gooey-goo i:nth-child(5) { top: 0.3em; right: 0; width: 1.2em; height: 1.2em; }
.btn-gooey-label { position: relative; }
.btn-gooey-svg {
position: absolute;
width: 0;
height: 0;
}
.btn-gooey:is(:hover, :focus-visible) .btn-gooey-goo i { animation: btn-gooey-pulse 1.8s ease-in-out 0.6s infinite; }
.btn-gooey:is(:hover, :focus-visible) .btn-gooey-goo i:nth-child(1) { translate: -0.3em -1.1em; }
.btn-gooey:is(:hover, :focus-visible) .btn-gooey-goo i:nth-child(2) { translate: 0 -1em; transition-delay: 0.06s; animation-duration: 2.2s; }
.btn-gooey:is(:hover, :focus-visible) .btn-gooey-goo i:nth-child(3) { translate: 0 1.15em; transition-delay: 0.03s; animation-duration: 1.6s; }
.btn-gooey:is(:hover, :focus-visible) .btn-gooey-goo i:nth-child(4) { translate: 0.2em 1em; transition-delay: 0.09s; animation-duration: 2s; }
.btn-gooey:is(:hover, :focus-visible) .btn-gooey-goo i:nth-child(5) { translate: 1em -0.2em; transition-delay: 0.12s; animation-duration: 2.4s; }
@keyframes btn-gooey-pulse {
50% { transform: scale(0.5); }
}
@media (prefers-reduced-motion: reduce) {
.btn-gooey, .btn-gooey *, .btn-gooey::before, .btn-gooey::after, .btn-gooey *::before, .btn-gooey *::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.