Open demo

CSS code

HTML
<div class="bp-motion">
<label class="bp-pause-label"><input type="checkbox" class="bp-pause"> Pause background motion</label>
<div><span class="lbl">Colour in motion</span><div class="bp-grid">
<div class="bp-flow bp-tile"><span class="bp-tag">Moving gradient</span></div>
<div class="bp-blobs bp-tile"><i></i><i></i><i></i><span class="bp-tag">Floating blobs</span></div>
</div></div>
<div><span class="lbl">Night skies</span><div class="bp-grid">
<div class="bp-aurora bp-tile"><span class="bp-tag">Aurora</span></div>
<div class="bp-stars bp-tile"><span class="bp-tag">Starfield</span></div>
</div></div>
</div>
CSS
/* Animated backgrounds that move only transform and opacity, so the browser composites them without repainting. They stop under prefers-reduced-motion and when the pause checkbox is ticked. */
@keyframes bp-slide{to{transform:translateX(-50%)}}
@keyframes bp-drift{50%{transform:translate(18%,12%) scale(1.12)}to{transform:translate(-10%,22%) scale(.92)}}
@keyframes bp-sway{from{transform:translateX(-8%) rotate(-8deg) scaleY(.9)}to{transform:translateX(8%) rotate(4deg) scaleY(1.15)}}
@keyframes bp-sway-b{from{transform:translate(6%,4%) rotate(6deg);opacity:.35}to{transform:translate(-6%,-4%) rotate(-4deg)}}
@keyframes bp-rise{to{transform:translateY(calc(var(--bp-size) * -1))}}
@keyframes bp-twinkle{from{opacity:.45}to{opacity:1}}
/* Moving gradient: a layer four tiles wide holds the gradient twice and slides left by one repeat, so the loop is seamless */
.bp-flow{--bp-bg:#6366f1;--bp-c1:#6366f1;--bp-c2:#ec4899;--bp-c3:#f59e0b;--bp-speed:14s;--bp-opacity:1;position:relative;isolation:isolate;overflow:hidden;background:var(--bp-bg)}
.bp-flow::before{content:"";position:absolute;inset:0 auto 0 0;width:400%;z-index:-1;pointer-events:none;opacity:var(--bp-opacity);background:linear-gradient(90deg,var(--bp-c1),var(--bp-c2),var(--bp-c3),var(--bp-c1)) 0 0/50% 100% repeat-x;will-change:transform;animation:bp-slide var(--bp-speed) linear infinite}
@media (prefers-reduced-motion:reduce){.bp-flow::before{animation:none}.bp-flow::after{animation:none}.bp-flow > i{animation:none}}
/* Floating blobs: three soft radial circles (no filter) drifting on transform only */
.bp-blobs{--bp-bg:#f8fafc;--bp-c1:#a5b4fc;--bp-c2:#f9a8d4;--bp-c3:#99f6e4;--bp-size:70%;--bp-speed:16s;--bp-opacity:.9;position:relative;isolation:isolate;overflow:hidden;background:var(--bp-bg)}
.bp-blobs > i{position:absolute;z-index:-1;width:var(--bp-size);aspect-ratio:1;border-radius:50%;pointer-events:none;opacity:var(--bp-opacity);will-change:transform;animation:bp-drift var(--bp-speed) ease-in-out infinite alternate}
.bp-blobs > i:nth-of-type(1){top:-30%;left:-15%;background:radial-gradient(closest-side,var(--bp-c1),transparent)}
.bp-blobs > i:nth-of-type(2){top:0;right:-25%;background:radial-gradient(closest-side,var(--bp-c2),transparent);animation-duration:calc(var(--bp-speed) * 1.3);animation-direction:alternate-reverse}
.bp-blobs > i:nth-of-type(3){bottom:-45%;left:25%;background:radial-gradient(closest-side,var(--bp-c3),transparent);animation-duration:calc(var(--bp-speed) * .8)}
@media (prefers-reduced-motion:reduce){.bp-blobs::before{animation:none}.bp-blobs::after{animation:none}.bp-blobs > i{animation:none}}
/* Aurora: two layers of soft bands that sway and fade, on transform and opacity */
.bp-aurora{--bp-bg:linear-gradient(#020617,#0b1d33);--bp-c1:#34d399;--bp-c2:#22d3ee;--bp-c3:#a78bfa;--bp-speed:12s;--bp-opacity:.75;position:relative;isolation:isolate;overflow:hidden;background:var(--bp-bg)}
.bp-aurora::before{content:"";position:absolute;inset:-25% -15%;z-index:-1;pointer-events:none;opacity:var(--bp-opacity);will-change:transform;background:radial-gradient(45% 20% at 30% 45%,var(--bp-c1),transparent 70%),radial-gradient(40% 16% at 66% 36%,var(--bp-c2),transparent 70%);animation:bp-sway var(--bp-speed) ease-in-out infinite alternate}
.bp-aurora::after{content:"";position:absolute;inset:-25% -15%;z-index:-1;pointer-events:none;opacity:var(--bp-opacity);will-change:transform,opacity;background:radial-gradient(35% 18% at 55% 62%,var(--bp-c3),transparent 70%);animation:bp-sway-b calc(var(--bp-speed) * 1.4) ease-in-out infinite alternate}
@media (prefers-reduced-motion:reduce){.bp-aurora::before{animation:none}.bp-aurora::after{animation:none}.bp-aurora > i{animation:none}}
/* Starfield: two star layers rising by exactly one tile per loop; the near layer also twinkles */
.bp-stars{--bp-bg:linear-gradient(#030712,#1e1b4b);--bp-star:#fff;--bp-size:200px;--bp-speed:40s;--bp-opacity:1;position:relative;isolation:isolate;overflow:hidden;background:var(--bp-bg)}
.bp-stars::before{content:"";position:absolute;inset:0 0 auto;height:calc(100% + var(--bp-size));z-index:-1;pointer-events:none;opacity:var(--bp-opacity);will-change:transform;background-image:radial-gradient(circle at 43% 21%,var(--bp-star) 1px,transparent 1.6px),radial-gradient(circle at 85% 8%,var(--bp-star) 0.8px,transparent 1.4px),radial-gradient(circle at 70% 14%,var(--bp-star) 1px,transparent 1.6px),radial-gradient(circle at 76% 9%,var(--bp-star) 1.2px,transparent 1.8px),radial-gradient(circle at 29% 6%,var(--bp-star) 0.8px,transparent 1.4px),radial-gradient(circle at 57% 55%,var(--bp-star) 0.8px,transparent 1.4px),radial-gradient(circle at 32% 13%,var(--bp-star) 1.2px,transparent 1.8px),radial-gradient(circle at 56% 9%,var(--bp-star) 1.2px,transparent 1.8px),radial-gradient(circle at 17% 30%,var(--bp-star) 1.2px,transparent 1.8px),radial-gradient(circle at 82% 76%,var(--bp-star) 0.8px,transparent 1.4px),radial-gradient(circle at 75% 76%,var(--bp-star) 1px,transparent 1.6px),radial-gradient(circle at 8% 30%,var(--bp-star) 0.8px,transparent 1.4px),radial-gradient(circle at 73% 19%,var(--bp-star) 1px,transparent 1.6px),radial-gradient(circle at 55% 20%,var(--bp-star) 1.2px,transparent 1.8px);background-size:var(--bp-size) var(--bp-size);animation:bp-rise var(--bp-speed) linear infinite}
.bp-stars::after{content:"";position:absolute;inset:0 0 auto;height:calc(100% + var(--bp-size));z-index:-1;pointer-events:none;will-change:transform,opacity;background-image:radial-gradient(circle at 59% 73%,var(--bp-star) 2px,transparent 2.6px),radial-gradient(circle at 59% 67%,var(--bp-star) 1.6px,transparent 2.2px),radial-gradient(circle at 25% 67%,var(--bp-star) 2px,transparent 2.6px),radial-gradient(circle at 82% 80%,var(--bp-star) 1.6px,transparent 2.2px),radial-gradient(circle at 14% 59%,var(--bp-star) 2px,transparent 2.6px),radial-gradient(circle at 20% 13%,var(--bp-star) 1.6px,transparent 2.2px);background-size:var(--bp-size) var(--bp-size);background-position:calc(var(--bp-size) / 3) 0;animation:bp-rise calc(var(--bp-speed) / 2) linear infinite,bp-twinkle 3s ease-in-out infinite alternate}
@media (prefers-reduced-motion:reduce){.bp-stars::before{animation:none}.bp-stars::after{animation:none}.bp-stars > i{animation:none}}
/* Pause switch: a checkbox anywhere inside .bp-motion freezes every animation in it (:has()). */
.bp-motion{display:flex;flex-direction:column;gap:32px}
.bp-motion:has(.bp-pause:checked) :is(.bp-flow,.bp-aurora,.bp-stars)::before,.bp-motion:has(.bp-pause:checked) :is(.bp-aurora,.bp-stars)::after,.bp-motion:has(.bp-pause:checked) .bp-blobs > i{animation-play-state:paused}
.bp-pause-label{display:inline-flex;align-items:center;gap:8px;align-self:flex-start;font-size:.875rem;line-height:20px;font-weight:600;color:#334155;cursor:pointer}
.bp-pause{width:16px;height:16px;margin:0;accent-color:#4f46e5}
.bp-pause:focus-visible{outline:2px solid #4f46e5;outline-offset:3px}
/* Demo tile and name chip */
.bp-tile{min-height:var(--bp-tile-h,180px);border-radius:16px;border:1px solid #dee2e6}
/* The name chip is an opaque scrim, so its text keeps 4.5:1 whatever pattern is underneath. */
.bp-tag{position:absolute;left:16px;bottom:16px;padding:4px 8px;border-radius:6px;background:#fff;color:#0f172a;font-size:.875rem;font-weight:600;line-height:1.5;box-shadow:0 2px 4px rgb(0 0 0 / .075)}
.bp-tile{--bp-tile-h:220px}

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div class="bp-motion">
<div class="form-check"><input class="form-check-input bp-pause" type="checkbox" id="bp-pause"><label class="form-check-label" for="bp-pause">Pause background motion</label></div>
<div><span class="lbl">Colour in motion</span><div class="row row-cols-1 row-cols-sm-2 g-3">
<div class="col"><div class="bp-flow bp-tile rounded-4 border"><span class="position-absolute start-0 bottom-0 m-3 px-2 py-1 rounded-2 bg-white text-dark small fw-semibold shadow-sm lh-base">Moving gradient</span></div></div>
<div class="col"><div class="bp-blobs bp-tile rounded-4 border"><i></i><i></i><i></i><span class="position-absolute start-0 bottom-0 m-3 px-2 py-1 rounded-2 bg-white text-dark small fw-semibold shadow-sm lh-base">Floating blobs</span></div></div>
</div></div>
<div><span class="lbl">Night skies</span><div class="row row-cols-1 row-cols-sm-2 g-3">
<div class="col"><div class="bp-aurora bp-tile rounded-4 border"><span class="position-absolute start-0 bottom-0 m-3 px-2 py-1 rounded-2 bg-white text-dark small fw-semibold shadow-sm lh-base">Aurora</span></div></div>
<div class="col"><div class="bp-stars bp-tile rounded-4 border"><span class="position-absolute start-0 bottom-0 m-3 px-2 py-1 rounded-2 bg-white text-dark small fw-semibold shadow-sm lh-base">Starfield</span></div></div>
</div></div>
</div>
CSS
/* Animated backgrounds that move only transform and opacity, so the browser composites them without repainting. They stop under prefers-reduced-motion and when the pause checkbox is ticked. */
@keyframes bp-slide{to{transform:translateX(-50%)}}
@keyframes bp-drift{50%{transform:translate(18%,12%) scale(1.12)}to{transform:translate(-10%,22%) scale(.92)}}
@keyframes bp-sway{from{transform:translateX(-8%) rotate(-8deg) scaleY(.9)}to{transform:translateX(8%) rotate(4deg) scaleY(1.15)}}
@keyframes bp-sway-b{from{transform:translate(6%,4%) rotate(6deg);opacity:.35}to{transform:translate(-6%,-4%) rotate(-4deg)}}
@keyframes bp-rise{to{transform:translateY(calc(var(--bp-size) * -1))}}
@keyframes bp-twinkle{from{opacity:.45}to{opacity:1}}
/* Moving gradient: a layer four tiles wide holds the gradient twice and slides left by one repeat, so the loop is seamless */
.bp-flow{--bp-bg:#6366f1;--bp-c1:#6366f1;--bp-c2:#ec4899;--bp-c3:#f59e0b;--bp-speed:14s;--bp-opacity:1;position:relative;isolation:isolate;overflow:hidden;background:var(--bp-bg)}
.bp-flow::before{content:"";position:absolute;inset:0 auto 0 0;width:400%;z-index:-1;pointer-events:none;opacity:var(--bp-opacity);background:linear-gradient(90deg,var(--bp-c1),var(--bp-c2),var(--bp-c3),var(--bp-c1)) 0 0/50% 100% repeat-x;will-change:transform;animation:bp-slide var(--bp-speed) linear infinite}
@media (prefers-reduced-motion:reduce){.bp-flow::before{animation:none}.bp-flow::after{animation:none}.bp-flow > i{animation:none}}
/* Floating blobs: three soft radial circles (no filter) drifting on transform only */
.bp-blobs{--bp-bg:#f8fafc;--bp-c1:#a5b4fc;--bp-c2:#f9a8d4;--bp-c3:#99f6e4;--bp-size:70%;--bp-speed:16s;--bp-opacity:.9;position:relative;isolation:isolate;overflow:hidden;background:var(--bp-bg)}
.bp-blobs > i{position:absolute;z-index:-1;width:var(--bp-size);aspect-ratio:1;border-radius:50%;pointer-events:none;opacity:var(--bp-opacity);will-change:transform;animation:bp-drift var(--bp-speed) ease-in-out infinite alternate}
.bp-blobs > i:nth-of-type(1){top:-30%;left:-15%;background:radial-gradient(closest-side,var(--bp-c1),transparent)}
.bp-blobs > i:nth-of-type(2){top:0;right:-25%;background:radial-gradient(closest-side,var(--bp-c2),transparent);animation-duration:calc(var(--bp-speed) * 1.3);animation-direction:alternate-reverse}
.bp-blobs > i:nth-of-type(3){bottom:-45%;left:25%;background:radial-gradient(closest-side,var(--bp-c3),transparent);animation-duration:calc(var(--bp-speed) * .8)}
@media (prefers-reduced-motion:reduce){.bp-blobs::before{animation:none}.bp-blobs::after{animation:none}.bp-blobs > i{animation:none}}
/* Aurora: two layers of soft bands that sway and fade, on transform and opacity */
.bp-aurora{--bp-bg:linear-gradient(#020617,#0b1d33);--bp-c1:#34d399;--bp-c2:#22d3ee;--bp-c3:#a78bfa;--bp-speed:12s;--bp-opacity:.75;position:relative;isolation:isolate;overflow:hidden;background:var(--bp-bg)}
.bp-aurora::before{content:"";position:absolute;inset:-25% -15%;z-index:-1;pointer-events:none;opacity:var(--bp-opacity);will-change:transform;background:radial-gradient(45% 20% at 30% 45%,var(--bp-c1),transparent 70%),radial-gradient(40% 16% at 66% 36%,var(--bp-c2),transparent 70%);animation:bp-sway var(--bp-speed) ease-in-out infinite alternate}
.bp-aurora::after{content:"";position:absolute;inset:-25% -15%;z-index:-1;pointer-events:none;opacity:var(--bp-opacity);will-change:transform,opacity;background:radial-gradient(35% 18% at 55% 62%,var(--bp-c3),transparent 70%);animation:bp-sway-b calc(var(--bp-speed) * 1.4) ease-in-out infinite alternate}
@media (prefers-reduced-motion:reduce){.bp-aurora::before{animation:none}.bp-aurora::after{animation:none}.bp-aurora > i{animation:none}}
/* Starfield: two star layers rising by exactly one tile per loop; the near layer also twinkles */
.bp-stars{--bp-bg:linear-gradient(#030712,#1e1b4b);--bp-star:#fff;--bp-size:200px;--bp-speed:40s;--bp-opacity:1;position:relative;isolation:isolate;overflow:hidden;background:var(--bp-bg)}
.bp-stars::before{content:"";position:absolute;inset:0 0 auto;height:calc(100% + var(--bp-size));z-index:-1;pointer-events:none;opacity:var(--bp-opacity);will-change:transform;background-image:radial-gradient(circle at 43% 21%,var(--bp-star) 1px,transparent 1.6px),radial-gradient(circle at 85% 8%,var(--bp-star) 0.8px,transparent 1.4px),radial-gradient(circle at 70% 14%,var(--bp-star) 1px,transparent 1.6px),radial-gradient(circle at 76% 9%,var(--bp-star) 1.2px,transparent 1.8px),radial-gradient(circle at 29% 6%,var(--bp-star) 0.8px,transparent 1.4px),radial-gradient(circle at 57% 55%,var(--bp-star) 0.8px,transparent 1.4px),radial-gradient(circle at 32% 13%,var(--bp-star) 1.2px,transparent 1.8px),radial-gradient(circle at 56% 9%,var(--bp-star) 1.2px,transparent 1.8px),radial-gradient(circle at 17% 30%,var(--bp-star) 1.2px,transparent 1.8px),radial-gradient(circle at 82% 76%,var(--bp-star) 0.8px,transparent 1.4px),radial-gradient(circle at 75% 76%,var(--bp-star) 1px,transparent 1.6px),radial-gradient(circle at 8% 30%,var(--bp-star) 0.8px,transparent 1.4px),radial-gradient(circle at 73% 19%,var(--bp-star) 1px,transparent 1.6px),radial-gradient(circle at 55% 20%,var(--bp-star) 1.2px,transparent 1.8px);background-size:var(--bp-size) var(--bp-size);animation:bp-rise var(--bp-speed) linear infinite}
.bp-stars::after{content:"";position:absolute;inset:0 0 auto;height:calc(100% + var(--bp-size));z-index:-1;pointer-events:none;will-change:transform,opacity;background-image:radial-gradient(circle at 59% 73%,var(--bp-star) 2px,transparent 2.6px),radial-gradient(circle at 59% 67%,var(--bp-star) 1.6px,transparent 2.2px),radial-gradient(circle at 25% 67%,var(--bp-star) 2px,transparent 2.6px),radial-gradient(circle at 82% 80%,var(--bp-star) 1.6px,transparent 2.2px),radial-gradient(circle at 14% 59%,var(--bp-star) 2px,transparent 2.6px),radial-gradient(circle at 20% 13%,var(--bp-star) 1.6px,transparent 2.2px);background-size:var(--bp-size) var(--bp-size);background-position:calc(var(--bp-size) / 3) 0;animation:bp-rise calc(var(--bp-speed) / 2) linear infinite,bp-twinkle 3s ease-in-out infinite alternate}
@media (prefers-reduced-motion:reduce){.bp-stars::before{animation:none}.bp-stars::after{animation:none}.bp-stars > i{animation:none}}
/* Pause switch: a checkbox anywhere inside .bp-motion freezes every animation in it (:has()). */
.bp-motion{display:flex;flex-direction:column;gap:32px}
.bp-motion:has(.bp-pause:checked) :is(.bp-flow,.bp-aurora,.bp-stars)::before,.bp-motion:has(.bp-pause:checked) :is(.bp-aurora,.bp-stars)::after,.bp-motion:has(.bp-pause:checked) .bp-blobs > i{animation-play-state:paused}
.bp-motion .form-check{--bs-form-check-bg:#fff;align-self:flex-start;min-height:0;margin:0;line-height:20px;font-size:.875rem;font-weight:600;color:#334155}
.bp-motion .form-check-input{--bs-border-color:#64748b}
.bp-motion .form-check-input:checked{background-color:#4f46e5;border-color:#4f46e5}
.bp-motion .form-check-input:focus-visible{outline:2px solid #4f46e5;outline-offset:3px;box-shadow:none}
/* Demo tile height; corners, border and the name chip use Bootstrap utilities */
.bp-tile{min-height:var(--bp-tile-h,180px)}
.bp-tile{--bp-tile-h:220px}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div class="bp-motion flex flex-col gap-8">
<label class="inline-flex cursor-pointer items-center gap-2 self-start text-sm leading-5 font-semibold text-[#334155]"><input type="checkbox" class="bp-pause m-0 size-4 accent-[#4f46e5] focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#4f46e5]"> Pause background motion</label>
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#5b6477] uppercase">Colour in motion</span><div class="grid grid-cols-1 gap-4 min-[576px]:grid-cols-2">
<div class="bp-flow relative min-h-[220px] overflow-hidden rounded-2xl border border-[#dee2e6]"><span class="absolute bottom-4 left-4 rounded-md bg-white px-2 py-1 text-sm leading-normal font-semibold text-[#0f172a] shadow-[0_2px_4px_rgb(0_0_0/.075)]">Moving gradient</span></div>
<div class="bp-blobs relative min-h-[220px] overflow-hidden rounded-2xl border border-[#dee2e6]"><i></i><i></i><i></i><span class="absolute bottom-4 left-4 rounded-md bg-white px-2 py-1 text-sm leading-normal font-semibold text-[#0f172a] shadow-[0_2px_4px_rgb(0_0_0/.075)]">Floating blobs</span></div>
</div></div>
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#5b6477] uppercase">Night skies</span><div class="grid grid-cols-1 gap-4 min-[576px]:grid-cols-2">
<div class="bp-aurora relative min-h-[220px] overflow-hidden rounded-2xl border border-[#dee2e6]"><span class="absolute bottom-4 left-4 rounded-md bg-white px-2 py-1 text-sm leading-normal font-semibold text-[#0f172a] shadow-[0_2px_4px_rgb(0_0_0/.075)]">Aurora</span></div>
<div class="bp-stars relative min-h-[220px] overflow-hidden rounded-2xl border border-[#dee2e6]"><span class="absolute bottom-4 left-4 rounded-md bg-white px-2 py-1 text-sm leading-normal font-semibold text-[#0f172a] shadow-[0_2px_4px_rgb(0_0_0/.075)]">Starfield</span></div>
</div></div>
</div>
CSS
/* Animated backgrounds that move only transform and opacity, so the browser composites them without repainting. They stop under prefers-reduced-motion and when the pause checkbox is ticked. Each background is a utility; pass colours and speed as arbitrary properties, e.g. [--bp-speed:20s]. */
@keyframes bp-slide{to{transform:translateX(-50%)}}
@keyframes bp-drift{50%{transform:translate(18%,12%) scale(1.12)}to{transform:translate(-10%,22%) scale(.92)}}
@keyframes bp-sway{from{transform:translateX(-8%) rotate(-8deg) scaleY(.9)}to{transform:translateX(8%) rotate(4deg) scaleY(1.15)}}
@keyframes bp-sway-b{from{transform:translate(6%,4%) rotate(6deg);opacity:.35}to{transform:translate(-6%,-4%) rotate(-4deg)}}
@keyframes bp-rise{to{transform:translateY(calc(var(--bp-size) * -1))}}
@keyframes bp-twinkle{from{opacity:.45}to{opacity:1}}
/* Moving gradient: a layer four tiles wide holds the gradient twice and slides left by one repeat, so the loop is seamless */
@utility bp-flow{
  position:relative;isolation:isolate;overflow:hidden;background:var(--bp-bg,#6366f1);
  &::before{content:"";position:absolute;inset:0 auto 0 0;width:400%;z-index:-1;pointer-events:none;opacity:var(--bp-opacity,1);background:linear-gradient(90deg,var(--bp-c1,#6366f1),var(--bp-c2,#ec4899),var(--bp-c3,#f59e0b),var(--bp-c1,#6366f1)) 0 0/50% 100% repeat-x;will-change:transform;animation:bp-slide var(--bp-speed,14s) linear infinite}
  @media (prefers-reduced-motion:reduce){&::before{animation:none}&::after{animation:none}& > i{animation:none}}
}
/* Floating blobs: three soft radial circles (no filter) drifting on transform only */
@utility bp-blobs{
  position:relative;isolation:isolate;overflow:hidden;background:var(--bp-bg,#f8fafc);
  & > i{position:absolute;z-index:-1;width:var(--bp-size,70%);aspect-ratio:1;border-radius:50%;pointer-events:none;opacity:var(--bp-opacity,.9);will-change:transform;animation:bp-drift var(--bp-speed,16s) ease-in-out infinite alternate}
  & > i:nth-of-type(1){top:-30%;left:-15%;background:radial-gradient(closest-side,var(--bp-c1,#a5b4fc),transparent)}
  & > i:nth-of-type(2){top:0;right:-25%;background:radial-gradient(closest-side,var(--bp-c2,#f9a8d4),transparent);animation-duration:calc(var(--bp-speed,16s) * 1.3);animation-direction:alternate-reverse}
  & > i:nth-of-type(3){bottom:-45%;left:25%;background:radial-gradient(closest-side,var(--bp-c3,#99f6e4),transparent);animation-duration:calc(var(--bp-speed,16s) * .8)}
  @media (prefers-reduced-motion:reduce){&::before{animation:none}&::after{animation:none}& > i{animation:none}}
}
/* Aurora: two layers of soft bands that sway and fade, on transform and opacity */
@utility bp-aurora{
  position:relative;isolation:isolate;overflow:hidden;background:var(--bp-bg,linear-gradient(#020617,#0b1d33));
  &::before{content:"";position:absolute;inset:-25% -15%;z-index:-1;pointer-events:none;opacity:var(--bp-opacity,.75);will-change:transform;background:radial-gradient(45% 20% at 30% 45%,var(--bp-c1,#34d399),transparent 70%),radial-gradient(40% 16% at 66% 36%,var(--bp-c2,#22d3ee),transparent 70%);animation:bp-sway var(--bp-speed,12s) ease-in-out infinite alternate}
  &::after{content:"";position:absolute;inset:-25% -15%;z-index:-1;pointer-events:none;opacity:var(--bp-opacity,.75);will-change:transform,opacity;background:radial-gradient(35% 18% at 55% 62%,var(--bp-c3,#a78bfa),transparent 70%);animation:bp-sway-b calc(var(--bp-speed,12s) * 1.4) ease-in-out infinite alternate}
  @media (prefers-reduced-motion:reduce){&::before{animation:none}&::after{animation:none}& > i{animation:none}}
}
/* Starfield: two star layers rising by exactly one tile per loop; the near layer also twinkles */
@utility bp-stars{
  position:relative;isolation:isolate;overflow:hidden;background:var(--bp-bg,linear-gradient(#030712,#1e1b4b));
  &::before{content:"";position:absolute;inset:0 0 auto;height:calc(100% + var(--bp-size,200px));z-index:-1;pointer-events:none;opacity:var(--bp-opacity,1);will-change:transform;background-image:radial-gradient(circle at 43% 21%,var(--bp-star,#fff) 1px,transparent 1.6px),radial-gradient(circle at 85% 8%,var(--bp-star,#fff) 0.8px,transparent 1.4px),radial-gradient(circle at 70% 14%,var(--bp-star,#fff) 1px,transparent 1.6px),radial-gradient(circle at 76% 9%,var(--bp-star,#fff) 1.2px,transparent 1.8px),radial-gradient(circle at 29% 6%,var(--bp-star,#fff) 0.8px,transparent 1.4px),radial-gradient(circle at 57% 55%,var(--bp-star,#fff) 0.8px,transparent 1.4px),radial-gradient(circle at 32% 13%,var(--bp-star,#fff) 1.2px,transparent 1.8px),radial-gradient(circle at 56% 9%,var(--bp-star,#fff) 1.2px,transparent 1.8px),radial-gradient(circle at 17% 30%,var(--bp-star,#fff) 1.2px,transparent 1.8px),radial-gradient(circle at 82% 76%,var(--bp-star,#fff) 0.8px,transparent 1.4px),radial-gradient(circle at 75% 76%,var(--bp-star,#fff) 1px,transparent 1.6px),radial-gradient(circle at 8% 30%,var(--bp-star,#fff) 0.8px,transparent 1.4px),radial-gradient(circle at 73% 19%,var(--bp-star,#fff) 1px,transparent 1.6px),radial-gradient(circle at 55% 20%,var(--bp-star,#fff) 1.2px,transparent 1.8px);background-size:var(--bp-size,200px) var(--bp-size,200px);animation:bp-rise var(--bp-speed,40s) linear infinite}
  &::after{content:"";position:absolute;inset:0 0 auto;height:calc(100% + var(--bp-size,200px));z-index:-1;pointer-events:none;will-change:transform,opacity;background-image:radial-gradient(circle at 59% 73%,var(--bp-star,#fff) 2px,transparent 2.6px),radial-gradient(circle at 59% 67%,var(--bp-star,#fff) 1.6px,transparent 2.2px),radial-gradient(circle at 25% 67%,var(--bp-star,#fff) 2px,transparent 2.6px),radial-gradient(circle at 82% 80%,var(--bp-star,#fff) 1.6px,transparent 2.2px),radial-gradient(circle at 14% 59%,var(--bp-star,#fff) 2px,transparent 2.6px),radial-gradient(circle at 20% 13%,var(--bp-star,#fff) 1.6px,transparent 2.2px);background-size:var(--bp-size,200px) var(--bp-size,200px);background-position:calc(var(--bp-size,200px) / 3) 0;animation:bp-rise calc(var(--bp-speed,40s) / 2) linear infinite,bp-twinkle 3s ease-in-out infinite alternate}
  @media (prefers-reduced-motion:reduce){&::before{animation:none}&::after{animation:none}& > i{animation:none}}
}
/* Pause checkbox: freezes every animation inside .bp-motion (:has()). */
.bp-motion:has(.bp-pause:checked) :is(.bp-flow,.bp-aurora,.bp-stars)::before,.bp-motion:has(.bp-pause:checked) :is(.bp-aurora,.bp-stars)::after,.bp-motion:has(.bp-pause:checked) .bp-blobs > i{animation-play-state:paused}

About this set

Four animated backgrounds that move only transform and opacity, so the browser can hand them to the compositor instead of repainting the gradients on every frame. Moving gradient is a layer four tiles wide that holds the gradient twice and slides left by exactly one repeat, so the loop has no jump. Floating blobs are three soft radial circles, drawn without a blur filter, drifting and scaling on different timings. Aurora is two layers of soft green, cyan and violet bands that sway, stretch and fade. Starfield has two star layers that rise by exactly one background tile per loop, and the nearer layer also twinkles through opacity. Use the moving gradient for banners and launch pages, the blobs behind sign-up and pricing sections, and the aurora or starfield behind dark heroes and event pages. Every animation stops under prefers-reduced-motion and shows a still frame. A Pause background motion checkbox above the tiles freezes all of them at once through :has(), which also covers the WCAG need to let people stop motion that lasts more than five seconds. Speeds and colours are custom properties.

What’s included

  • Moving gradient, floating blobs, aurora and starfield
  • Transform and opacity only, no animated gradients or filters
  • Seamless loops: layers move by exactly one repeat
  • Stops under prefers-reduced-motion and with a pause checkbox
  • Plain CSS, Bootstrap 5 and Tailwind CSS 4 versions of the same design

Accessibility

All animation is removed under prefers-reduced-motion, and a labelled Pause background motion checkbox freezes every background in the group, so motion can be stopped as WCAG 2.2.2 requires. The checkbox shows a 2px outline on keyboard focus. The backgrounds are decorative, and name labels are opaque chips above 4.5:1.

Customise it

Set --bp-speed for the loop length, --bp-c1 to --bp-c3 for colours, --bp-bg for the ground, --bp-size for blob or star tile size and --bp-opacity for strength. In Tailwind pass them as arbitrary properties on bp-flow, bp-blobs, bp-aurora or bp-stars.