Pastel wax blobs pinch off a glowing pool, drift up, stretch and sink back like a real lava lamp, lit from within. CSS blur on each shaded blob, then an inline SVG alpha threshold makes the goo.
Also known as: lava lamp css, lava lamp animation, gooey blob background, metaball css.
<div class="bgx-gooey-lava" aria-hidden="true"> <svg class="bgx-gooey-lava-defs" width="0" height="0"><filter id="bgx-gooey-lava-goo"><feColorMatrix values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 14 -5.5" /></filter></svg> <span class="bgx-gooey-lava-tank"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span> </div>
.bgx-gooey-lava {
--c: var(--bgx-color, #3b82f6);
font-size: var(--bgx-size, 16px);
position: absolute;
inset: 0;
overflow: hidden;
background:
radial-gradient(ellipse 70% 60% at 50% 105%, oklch(from var(--c) 0.9 0.07 calc(h + 20) / 0.9), transparent),
linear-gradient(oklch(from var(--c) 0.975 0.012 calc(h - 20)), oklch(from var(--c) 0.95 0.03 calc(h + 30)));}
.bgx-gooey-lava-defs {
position: absolute;
}
.bgx-gooey-lava-tank {
position: absolute;
inset: 0;
filter: url(#bgx-gooey-lava-goo) drop-shadow(0 0 0.8em oklch(from var(--c) 0.75 0.14 calc(h + 10) / 0.45));
}
.bgx-gooey-lava-tank::after {
content: "";
position: absolute;
left: 50%;
border-radius: 50%;
filter: blur(0.7em);
}
.bgx-gooey-lava-tank::after {
bottom: -3.2em;
width: 120%;
height: 6em;
translate: -50% 0;
background: radial-gradient(ellipse at 50% 20%, oklch(from var(--c) 0.86 0.1 calc(h + 30)), oklch(from var(--c) 0.7 0.16 calc(h + 0)) 60%, oklch(from var(--c) 0.62 0.17 calc(h - 30)));
}
.bgx-gooey-lava-tank span {
position: absolute;
top: 0;
bottom: 0;
left: var(--x);
width: var(--s);
animation: bgx-gooey-lava-rise var(--t) cubic-bezier(0.45, 0, 0.55, 1) var(--d) infinite alternate;
}
.bgx-gooey-lava-tank span::before {
content: "";
position: absolute;
left: 0;
bottom: 0;
width: var(--s);
height: var(--s);
border-radius: 50%;
background: radial-gradient(circle at 38% 30%, var(--l), var(--a) 45%, var(--b));
filter: blur(0.6em);
animation: bgx-gooey-lava-stretch calc(var(--t) / 2) cubic-bezier(0.45, 0, 0.55, 1) var(--d) infinite alternate;
}
.bgx-gooey-lava-tank span:nth-child(1) { --x: 2%; --s: 5.02em; --t: 11.3s; --d: -7.8s; --l: oklch(from var(--c) 0.95 0.05 calc(h + 0)); --a: oklch(from var(--c) 0.8 0.13 calc(h + 0)); --b: oklch(from var(--c) 0.64 0.17 calc(h + 30)); --k: 0.76; }
.bgx-gooey-lava-tank span:nth-child(2) { --x: 14.7%; --s: 4.55em; --t: 15.8s; --d: -14.3s; --l: oklch(from var(--c) 0.95 0.05 calc(h + 40)); --a: oklch(from var(--c) 0.8 0.13 calc(h + 40)); --b: oklch(from var(--c) 0.64 0.17 calc(h + 70)); --k: 0.75; }
.bgx-gooey-lava-tank span:nth-child(3) { --x: 26.6%; --s: 4.12em; --t: 13.8s; --d: -5.7s; --l: oklch(from var(--c) 0.95 0.05 calc(h - 40)); --a: oklch(from var(--c) 0.8 0.13 calc(h - 40)); --b: oklch(from var(--c) 0.64 0.17 calc(h - 10)); --k: 0.53; }
.bgx-gooey-lava-tank span:nth-child(4) { --x: 39.1%; --s: 3.44em; --t: 15s; --d: -3.1s; --l: oklch(from var(--c) 0.95 0.05 calc(h + 20)); --a: oklch(from var(--c) 0.8 0.13 calc(h + 20)); --b: oklch(from var(--c) 0.64 0.17 calc(h + 50)); --k: 0.67; }
.bgx-gooey-lava-tank span:nth-child(5) { --x: 51.5%; --s: 4.68em; --t: 10s; --d: -4s; --l: oklch(from var(--c) 0.95 0.05 calc(h - 60)); --a: oklch(from var(--c) 0.8 0.13 calc(h - 60)); --b: oklch(from var(--c) 0.64 0.17 calc(h - 30)); --k: 0.49; }
.bgx-gooey-lava-tank span:nth-child(6) { --x: 62.3%; --s: 5.41em; --t: 12.4s; --d: -5.6s; --l: oklch(from var(--c) 0.95 0.05 calc(h + 60)); --a: oklch(from var(--c) 0.8 0.13 calc(h + 60)); --b: oklch(from var(--c) 0.64 0.17 calc(h + 90)); --k: 0.46; }
.bgx-gooey-lava-tank span:nth-child(7) { --x: 74.9%; --s: 5.44em; --t: 12.7s; --d: -9.7s; --l: oklch(from var(--c) 0.95 0.05 calc(h - 20)); --a: oklch(from var(--c) 0.8 0.13 calc(h - 20)); --b: oklch(from var(--c) 0.64 0.17 calc(h + 10)); --k: 0.63; }
.bgx-gooey-lava-tank span:nth-child(8) { --x: 88.5%; --s: 4.08em; --t: 12s; --d: -2.1s; --l: oklch(from var(--c) 0.95 0.05 calc(h + 30)); --a: oklch(from var(--c) 0.8 0.13 calc(h + 30)); --b: oklch(from var(--c) 0.64 0.17 calc(h + 60)); --k: 0.65; }
@keyframes bgx-gooey-lava-rise {
from { transform: translateY(1.6em); }
to { transform: translateY(calc((-100% + var(--s) + 0.6em) * var(--k))); }
}
@keyframes bgx-gooey-lava-stretch {
from { transform: scale(1, 1); }
to { transform: scale(0.82, 1.22); }
}
@media (prefers-reduced-motion: reduce) {
.bgx-gooey-lava, .bgx-gooey-lava::before, .bgx-gooey-lava::after, .bgx-gooey-lava *, .bgx-gooey-lava *::before, .bgx-gooey-lava *::after { animation-play-state: paused; }
}
@media (prefers-reduced-motion: reduce) {
.bgx-gooey-lava, .bgx-gooey-lava *, .bgx-gooey-lava::before, .bgx-gooey-lava::after, .bgx-gooey-lava *::before, .bgx-gooey-lava *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--bgx-color on the element or a parent.
Currently previewing #3b82f6.--bgx-size. Every other dimension is in
em, so the whole thing scales together. Currently previewing 16px.transition or animation
shorthand. Staggered delays are relative, so they keep their rhythm.