Pigment blooms spread across warm paper one after another, each with the darker rim of real watercolor, mixing where they overlap and fading away. The ragged edges come from an inline SVG filter.
Also known as: watercolor background css, ink bloom animation, paint spread background css.
<div class="bgx-watercolor-bloom" aria-hidden="true">
<svg class="bgx-watercolor-bloom-defs" width="0" height="0">
<filter id="bgx-watercolor-bloom-edge"><feTurbulence type="fractalNoise" baseFrequency="0.04" numOctaves="2" seed="3" /><feDisplacementMap in="SourceGraphic" scale="16" xChannelSelector="R" yChannelSelector="G" /></filter>
</svg>
<span></span><span></span><span></span><span></span><span></span>
</div>.bgx-watercolor-bloom {
--c: var(--bgx-color, #3b82f6);
font-size: var(--bgx-size, 16px);
position: absolute;
inset: 0;
overflow: hidden;
background: #fbf8f2;
isolation: isolate;
}
.bgx-watercolor-bloom-defs {
position: absolute;
}
.bgx-watercolor-bloom span {
position: absolute;
left: var(--x);
top: var(--y);
width: var(--w);
aspect-ratio: 1.2;
translate: -50% -50%;
border-radius: 50%;
background: radial-gradient(closest-side, var(--p) 0%, var(--p) 55%, var(--rim) 86%, transparent 95%);
filter: url(#bgx-watercolor-bloom-edge);
mix-blend-mode: multiply;
opacity: 0;
animation: bgx-watercolor-bloom-spread 10s cubic-bezier(0.2, 0.7, 0.3, 1) var(--d) infinite;
}
.bgx-watercolor-bloom span:nth-child(1) { --x: 22%; --y: 38%; --w: 15em; --d: 0s; --p: oklch(from var(--c) 0.8 0.1 calc(h + 0) / 0.45); --rim: oklch(from var(--c) 0.6 0.15 calc(h + 0) / 0.7); }
.bgx-watercolor-bloom span:nth-child(2) { --x: 60%; --y: 62%; --w: 17em; --d: -2s; --p: oklch(from var(--c) 0.82 0.1 calc(h + 60) / 0.45); --rim: oklch(from var(--c) 0.62 0.16 calc(h + 60) / 0.7); }
.bgx-watercolor-bloom span:nth-child(3) { --x: 82%; --y: 28%; --w: 13em; --d: -4s; --p: oklch(from var(--c) 0.86 0.1 calc(h - 60) / 0.45); --rim: oklch(from var(--c) 0.66 0.13 calc(h - 60) / 0.7); }
.bgx-watercolor-bloom span:nth-child(4) { --x: 40%; --y: 20%; --w: 12em; --d: -6s; --p: oklch(from var(--c) 0.86 0.1 calc(h + 130) / 0.45); --rim: oklch(from var(--c) 0.68 0.14 calc(h + 130) / 0.7); }
.bgx-watercolor-bloom span:nth-child(5) { --x: 12%; --y: 78%; --w: 14em; --d: -8s; --p: oklch(from var(--c) 0.8 0.1 calc(h + 30) / 0.45); --rim: oklch(from var(--c) 0.6 0.15 calc(h + 30) / 0.7); }
@keyframes bgx-watercolor-bloom-spread {
0% { transform: scale(0.3) rotate(0deg); opacity: 0; }
10% { opacity: 1; }
55% { transform: scale(1) rotate(12deg); opacity: 1; }
100% { transform: scale(1.06) rotate(16deg); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.bgx-watercolor-bloom, .bgx-watercolor-bloom *, .bgx-watercolor-bloom::before, .bgx-watercolor-bloom::after, .bgx-watercolor-bloom *::before, .bgx-watercolor-bloom *::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.