21 / 25
All 25 in 25 CSS Background Animations
- 01Full-Screen Linear Gradient Shift
- 02Cursor Spotlight Follow Effect
- 03Animated Dot Grid Pattern
- 04Grain & Noise Texture Overlay
- 05Topographic Contour Line Flow
- 06Diagonal Moving Stripes Pattern
- 07Animated SVG Wave Layers
- 08Plasma / Lava Lamp Fluid Effect
- 09Animated Bokeh Light Blur
- 10Particle Constellation Network
- 11Starfield Warp Speed Transition
- 12CRT Scanline Overlay Background
- 13Animated Fog / Mist Drift Background
- 14Animated Mesh Gradient Background
- 15Aurora Borealis Glow Background
- 16Interactive Water Ripple Background
- 17Animated Halftone Dot Pattern
- 18Morphing Organic Blob Animation
- 19Floating Glassmorphism Orbs
- 20Matrix Digital Rain Effect
- 21Synthwave 3D Retro Grid
- 22Starry Night Sky & Falling Starfall
- 23Floating Geometric Shapes
- 24Floating Bubble Background
- 25Falling Snow / Weather Effect
Synthwave 3D Retro Grid
The outrun horizon: a real 3D floor built with perspective and rotateX, scrolling toward you forever, under a slit-scanned neon sun and a chrome horizon glow. The infinite scroll is a single trick — translate the grid by exactly one cell in Z-space — and because the plane is genuinely rotated in 3D, the perspective convergence is correct rather than faked with a skew.
Build UI like this from scratch: Flexbox, Grid, animations & responsive CSS
ZTM · The CSS Bootcamp · 30‑day money-back guarantee · Affiliate link
The code
<section class="bga-21" aria-label="Synthwave 3D retro grid background demo">
<div class="bga-21__sky" aria-hidden="true"></div>
<div class="bga-21__stars" aria-hidden="true"></div>
<div class="bga-21__sun" aria-hidden="true"></div>
<div class="bga-21__haze" aria-hidden="true"></div>
<div class="bga-21__scene" aria-hidden="true">
<div class="bga-21__floor"></div>
</div>
<div class="bga-21__horizon" aria-hidden="true"></div>
<div class="bga-21__stage">
<div class="bga-21__copy">
<p class="bga-21__eyebrow">Perspective · rotateX(78deg) · 21</p>
<h1 class="bga-21__title">OUTRUN</h1>
<p class="bga-21__sub">A genuinely 3D plane, not a skew. The velocity gradient from near to far comes free with the projection — that is the part your eye reads as speed.</p>
<div class="bga-21__gauges">
<span>perspective <b>340px</b></span>
<span>cell <b>60px</b></span>
<span>loop <b>1.4s</b></span>
</div>
</div>
</div>
</section><section class="bga-21" aria-label="Synthwave 3D retro grid background demo">
<div class="bga-21__sky" aria-hidden="true"></div>
<div class="bga-21__stars" aria-hidden="true"></div>
<div class="bga-21__sun" aria-hidden="true"></div>
<div class="bga-21__haze" aria-hidden="true"></div>
<div class="bga-21__scene" aria-hidden="true">
<div class="bga-21__floor"></div>
</div>
<div class="bga-21__horizon" aria-hidden="true"></div>
<div class="bga-21__stage">
<div class="bga-21__copy">
<p class="bga-21__eyebrow">Perspective · rotateX(78deg) · 21</p>
<h1 class="bga-21__title">OUTRUN</h1>
<p class="bga-21__sub">A genuinely 3D plane, not a skew. The velocity gradient from near to far comes free with the projection — that is the part your eye reads as speed.</p>
<div class="bga-21__gauges">
<span>perspective <b>340px</b></span>
<span>cell <b>60px</b></span>
<span>loop <b>1.4s</b></span>
</div>
</div>
</div>
</section>.bga-21,
.bga-21 *,
.bga-21 *::before,
.bga-21 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.bga-21 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
overflow: hidden;
isolation: isolate;
--bga-21-line: oklch(0.78 0.22 330);
--bga-21-glow: oklch(0.72 0.2 300);
--bga-21-hot: oklch(0.86 0.2 60);
background: oklch(0.1 0.04 292);
color: oklch(0.98 0.02 320);
font-family: ui-sans-serif,system-ui,-apple-system,'Segoe UI',sans-serif;
-webkit-font-smoothing: antialiased;
}
.bga-21__sky {
position: absolute;
inset: 0 0 42% 0;
background: linear-gradient(180deg,oklch(0.1 0.05 292) 0%,oklch(0.2 0.1 300) 44%,oklch(0.4 0.16 330) 78%,oklch(0.56 0.2 350) 100%);
}
.bga-21__stars {
position: absolute;
inset: 0 0 52% 0;
opacity: .6;
background-image: radial-gradient(oklch(1 0 0/.85) .9px,transparent 1.1px),radial-gradient(oklch(0.9 0.06 320/.6) .7px,transparent 1px);
background-size: 130px 130px,77px 77px;
background-position: 0 0,38px 52px;
animation: bga-21-tw 6s ease-in-out infinite alternate;
}
@keyframes bga-21-tw {
from {
opacity: .35;
}
to {
opacity: .75;
}
}
.bga-21__sun {
position: absolute;
left: 50%;
bottom: 41%;
translate: -50% 0;
width: min(46vmin,380px);
aspect-ratio: 1;
border-radius: 50%;
background: linear-gradient(180deg,var(--bga-21-hot) 0%,oklch(0.78 0.22 30) 42%,oklch(0.62 0.25 8) 74%,oklch(0.52 0.24 340) 100%);
-webkit-mask-image: linear-gradient(oklch(0 0 0) 0 46%,transparent 46%),repeating-linear-gradient(oklch(0 0 0) 0 9px,transparent 9px 15px);
mask-image: linear-gradient(oklch(0 0 0) 0 46%,transparent 46%),repeating-linear-gradient(oklch(0 0 0) 0 9px,transparent 9px 15px);
-webkit-mask-composite: source-over;
mask-composite: add;
filter: drop-shadow(0 0 70px color-mix(in oklab,var(--bga-21-hot) 55%,transparent));
animation: bga-21-pulse 7s ease-in-out infinite alternate;
}
@keyframes bga-21-pulse {
to {
filter: drop-shadow(0 0 110px color-mix(in oklab,var(--bga-21-hot) 75%,transparent));
}
}
.bga-21__haze {
position: absolute;
inset: 0 0 30% 0;
pointer-events: none;
mix-blend-mode: screen;
opacity: .5;
background: radial-gradient(70% 40% at 50% 100%,color-mix(in oklab,var(--bga-21-glow) 55%,transparent),transparent 72%);
}
.bga-21__scene {
position: absolute;
inset-inline: -30%;
inset-block-end: 0;
height: 44%;
perspective: 340px;
perspective-origin: 50% 0%;
-webkit-mask-image: linear-gradient(transparent,oklch(0 0 0) 16%);
mask-image: linear-gradient(transparent,oklch(0 0 0) 16%);
}
.bga-21__floor {
position: absolute;
inset: 0;
transform: rotateX(78deg);
transform-origin: 50% 0%;
background-color: oklch(0.12 0.05 300);
background-image: linear-gradient(color-mix(in oklab,var(--bga-21-line) 90%,transparent) 2px,transparent 2px), linear-gradient(90deg,color-mix(in oklab,var(--bga-21-line) 90%,transparent) 2px,transparent 2px);
background-size: 60px 60px;
animation: bga-21-run 1.4s linear infinite;
}
@keyframes bga-21-run {
to {
background-position: 0 60px;
}
}
.bga-21__horizon {
position: absolute;
inset-inline: 0;
bottom: 43.4%;
height: 2px;
background: linear-gradient(90deg,transparent,var(--bga-21-hot),oklch(1 0 0),var(--bga-21-line),transparent);
box-shadow: 0 0 26px 5px color-mix(in oklab,var(--bga-21-line) 55%,transparent);
}
.bga-21__stage {
position: relative;
z-index: 5;
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
padding: clamp(22px,5vw,68px) clamp(22px,5vw,68px) 46vh;
}
.bga-21__copy {
width: min(100%,660px);
display: grid;
gap: 14px;
justify-items: center;
text-align: center;
}
.bga-21__eyebrow {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
letter-spacing: .24em;
text-transform: uppercase;
color: oklch(0.9 0.12 320);
}
.bga-21__title {
font-size: clamp(44px,13vw,142px);
font-weight: 800;
letter-spacing: .06em;
line-height: .9;
background: linear-gradient(180deg,oklch(0.99 0.03 90) 0 42%,var(--bga-21-hot) 52%,oklch(0.66 0.25 350) 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
filter: drop-shadow(0 0 26px color-mix(in oklab,var(--bga-21-line) 55%,transparent));
}
.bga-21__sub {
max-width: 48ch;
font-size: clamp(13px,1.3vw,16px);
line-height: 1.6;
color: oklch(0.95 0.02 320/.8);
text-wrap: pretty;
text-shadow: 0 1px 18px oklch(0.1 0.04 300/.9);
}
.bga-21__gauges {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
margin-top: 6px;
}
.bga-21__gauges span {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
padding: 7px 13px;
color: oklch(1 0 0/.7);
background: oklch(0.2 0.06 300/.45);
border: 1px solid color-mix(in oklab,var(--bga-21-line) 34%,transparent);
}
.bga-21__gauges b {
color: oklch(0.94 0.1 330);
}
@media (prefers-reduced-motion: reduce) {
.bga-21__floor,
.bga-21__sun,
.bga-21__stars {
animation: none;
}
}.bga-21,
.bga-21 *,
.bga-21 *::before,
.bga-21 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.bga-21 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
overflow: hidden;
isolation: isolate;
--bga-21-line: oklch(0.78 0.22 330);
--bga-21-glow: oklch(0.72 0.2 300);
--bga-21-hot: oklch(0.86 0.2 60);
background: oklch(0.1 0.04 292);
color: oklch(0.98 0.02 320);
font-family: ui-sans-serif,system-ui,-apple-system,'Segoe UI',sans-serif;
-webkit-font-smoothing: antialiased;
}
.bga-21__sky {
position: absolute;
inset: 0 0 42% 0;
background: linear-gradient(180deg,oklch(0.1 0.05 292) 0%,oklch(0.2 0.1 300) 44%,oklch(0.4 0.16 330) 78%,oklch(0.56 0.2 350) 100%);
}
.bga-21__stars {
position: absolute;
inset: 0 0 52% 0;
opacity: .6;
background-image: radial-gradient(oklch(1 0 0/.85) .9px,transparent 1.1px),radial-gradient(oklch(0.9 0.06 320/.6) .7px,transparent 1px);
background-size: 130px 130px,77px 77px;
background-position: 0 0,38px 52px;
animation: bga-21-tw 6s ease-in-out infinite alternate;
}
@keyframes bga-21-tw {
from {
opacity: .35;
}
to {
opacity: .75;
}
}
.bga-21__sun {
position: absolute;
left: 50%;
bottom: 41%;
translate: -50% 0;
width: min(46vmin,380px);
aspect-ratio: 1;
border-radius: 50%;
background: linear-gradient(180deg,var(--bga-21-hot) 0%,oklch(0.78 0.22 30) 42%,oklch(0.62 0.25 8) 74%,oklch(0.52 0.24 340) 100%);
-webkit-mask-image: linear-gradient(oklch(0 0 0) 0 46%,transparent 46%),repeating-linear-gradient(oklch(0 0 0) 0 9px,transparent 9px 15px);
mask-image: linear-gradient(oklch(0 0 0) 0 46%,transparent 46%),repeating-linear-gradient(oklch(0 0 0) 0 9px,transparent 9px 15px);
-webkit-mask-composite: source-over;
mask-composite: add;
filter: drop-shadow(0 0 70px color-mix(in oklab,var(--bga-21-hot) 55%,transparent));
animation: bga-21-pulse 7s ease-in-out infinite alternate;
}
@keyframes bga-21-pulse {
to {
filter: drop-shadow(0 0 110px color-mix(in oklab,var(--bga-21-hot) 75%,transparent));
}
}
.bga-21__haze {
position: absolute;
inset: 0 0 30% 0;
pointer-events: none;
mix-blend-mode: screen;
opacity: .5;
background: radial-gradient(70% 40% at 50% 100%,color-mix(in oklab,var(--bga-21-glow) 55%,transparent),transparent 72%);
}
.bga-21__scene {
position: absolute;
inset-inline: -30%;
inset-block-end: 0;
height: 44%;
perspective: 340px;
perspective-origin: 50% 0%;
-webkit-mask-image: linear-gradient(transparent,oklch(0 0 0) 16%);
mask-image: linear-gradient(transparent,oklch(0 0 0) 16%);
}
.bga-21__floor {
position: absolute;
inset: 0;
transform: rotateX(78deg);
transform-origin: 50% 0%;
background-color: oklch(0.12 0.05 300);
background-image: linear-gradient(color-mix(in oklab,var(--bga-21-line) 90%,transparent) 2px,transparent 2px), linear-gradient(90deg,color-mix(in oklab,var(--bga-21-line) 90%,transparent) 2px,transparent 2px);
background-size: 60px 60px;
animation: bga-21-run 1.4s linear infinite;
}
@keyframes bga-21-run {
to {
background-position: 0 60px;
}
}
.bga-21__horizon {
position: absolute;
inset-inline: 0;
bottom: 43.4%;
height: 2px;
background: linear-gradient(90deg,transparent,var(--bga-21-hot),oklch(1 0 0),var(--bga-21-line),transparent);
box-shadow: 0 0 26px 5px color-mix(in oklab,var(--bga-21-line) 55%,transparent);
}
.bga-21__stage {
position: relative;
z-index: 5;
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
padding: clamp(22px,5vw,68px) clamp(22px,5vw,68px) 46vh;
}
.bga-21__copy {
width: min(100%,660px);
display: grid;
gap: 14px;
justify-items: center;
text-align: center;
}
.bga-21__eyebrow {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
letter-spacing: .24em;
text-transform: uppercase;
color: oklch(0.9 0.12 320);
}
.bga-21__title {
font-size: clamp(44px,13vw,142px);
font-weight: 800;
letter-spacing: .06em;
line-height: .9;
background: linear-gradient(180deg,oklch(0.99 0.03 90) 0 42%,var(--bga-21-hot) 52%,oklch(0.66 0.25 350) 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
filter: drop-shadow(0 0 26px color-mix(in oklab,var(--bga-21-line) 55%,transparent));
}
.bga-21__sub {
max-width: 48ch;
font-size: clamp(13px,1.3vw,16px);
line-height: 1.6;
color: oklch(0.95 0.02 320/.8);
text-wrap: pretty;
text-shadow: 0 1px 18px oklch(0.1 0.04 300/.9);
}
.bga-21__gauges {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
margin-top: 6px;
}
.bga-21__gauges span {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
padding: 7px 13px;
color: oklch(1 0 0/.7);
background: oklch(0.2 0.06 300/.45);
border: 1px solid color-mix(in oklab,var(--bga-21-line) 34%,transparent);
}
.bga-21__gauges b {
color: oklch(0.94 0.1 330);
}
@media (prefers-reduced-motion: reduce) {
.bga-21__floor,
.bga-21__sun,
.bga-21__stars {
animation: none;
}
}Here's a working CSS Background Animation from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Synthwave 3D Retro Grid
Source: https://codefronts.com/motion/css-background-animations/retro-cyberpunk-grid-synthwave-horizon/
The outrun horizon: a real 3D floor built with perspective and rotateX, scrolling toward you forever, under a slit-scanned neon sun and a chrome horizon glow. The infinite scroll is a single trick — translate the grid by exactly one cell in Z-space — and because the plane is genuinely rotated in 3D, the perspective convergence is correct rather than faked with a skew.
## HTML
```html
<section class="bga-21" aria-label="Synthwave 3D retro grid background demo">
<div class="bga-21__sky" aria-hidden="true"></div>
<div class="bga-21__stars" aria-hidden="true"></div>
<div class="bga-21__sun" aria-hidden="true"></div>
<div class="bga-21__haze" aria-hidden="true"></div>
<div class="bga-21__scene" aria-hidden="true">
<div class="bga-21__floor"></div>
</div>
<div class="bga-21__horizon" aria-hidden="true"></div>
<div class="bga-21__stage">
<div class="bga-21__copy">
<p class="bga-21__eyebrow">Perspective · rotateX(78deg) · 21</p>
<h1 class="bga-21__title">OUTRUN</h1>
<p class="bga-21__sub">A genuinely 3D plane, not a skew. The velocity gradient from near to far comes free with the projection — that is the part your eye reads as speed.</p>
<div class="bga-21__gauges">
<span>perspective <b>340px</b></span>
<span>cell <b>60px</b></span>
<span>loop <b>1.4s</b></span>
</div>
</div>
</div>
</section>
```
## CSS
```css
.bga-21,
.bga-21 *,
.bga-21 *::before,
.bga-21 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.bga-21 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
overflow: hidden;
isolation: isolate;
--bga-21-line: oklch(0.78 0.22 330);
--bga-21-glow: oklch(0.72 0.2 300);
--bga-21-hot: oklch(0.86 0.2 60);
background: oklch(0.1 0.04 292);
color: oklch(0.98 0.02 320);
font-family: ui-sans-serif,system-ui,-apple-system,'Segoe UI',sans-serif;
-webkit-font-smoothing: antialiased;
}
.bga-21__sky {
position: absolute;
inset: 0 0 42% 0;
background: linear-gradient(180deg,oklch(0.1 0.05 292) 0%,oklch(0.2 0.1 300) 44%,oklch(0.4 0.16 330) 78%,oklch(0.56 0.2 350) 100%);
}
.bga-21__stars {
position: absolute;
inset: 0 0 52% 0;
opacity: .6;
background-image: radial-gradient(oklch(1 0 0/.85) .9px,transparent 1.1px),radial-gradient(oklch(0.9 0.06 320/.6) .7px,transparent 1px);
background-size: 130px 130px,77px 77px;
background-position: 0 0,38px 52px;
animation: bga-21-tw 6s ease-in-out infinite alternate;
}
@keyframes bga-21-tw {
from {
opacity: .35;
}
to {
opacity: .75;
}
}
.bga-21__sun {
position: absolute;
left: 50%;
bottom: 41%;
translate: -50% 0;
width: min(46vmin,380px);
aspect-ratio: 1;
border-radius: 50%;
background: linear-gradient(180deg,var(--bga-21-hot) 0%,oklch(0.78 0.22 30) 42%,oklch(0.62 0.25 8) 74%,oklch(0.52 0.24 340) 100%);
-webkit-mask-image: linear-gradient(oklch(0 0 0) 0 46%,transparent 46%),repeating-linear-gradient(oklch(0 0 0) 0 9px,transparent 9px 15px);
mask-image: linear-gradient(oklch(0 0 0) 0 46%,transparent 46%),repeating-linear-gradient(oklch(0 0 0) 0 9px,transparent 9px 15px);
-webkit-mask-composite: source-over;
mask-composite: add;
filter: drop-shadow(0 0 70px color-mix(in oklab,var(--bga-21-hot) 55%,transparent));
animation: bga-21-pulse 7s ease-in-out infinite alternate;
}
@keyframes bga-21-pulse {
to {
filter: drop-shadow(0 0 110px color-mix(in oklab,var(--bga-21-hot) 75%,transparent));
}
}
.bga-21__haze {
position: absolute;
inset: 0 0 30% 0;
pointer-events: none;
mix-blend-mode: screen;
opacity: .5;
background: radial-gradient(70% 40% at 50% 100%,color-mix(in oklab,var(--bga-21-glow) 55%,transparent),transparent 72%);
}
.bga-21__scene {
position: absolute;
inset-inline: -30%;
inset-block-end: 0;
height: 44%;
perspective: 340px;
perspective-origin: 50% 0%;
-webkit-mask-image: linear-gradient(transparent,oklch(0 0 0) 16%);
mask-image: linear-gradient(transparent,oklch(0 0 0) 16%);
}
.bga-21__floor {
position: absolute;
inset: 0;
transform: rotateX(78deg);
transform-origin: 50% 0%;
background-color: oklch(0.12 0.05 300);
background-image: linear-gradient(color-mix(in oklab,var(--bga-21-line) 90%,transparent) 2px,transparent 2px), linear-gradient(90deg,color-mix(in oklab,var(--bga-21-line) 90%,transparent) 2px,transparent 2px);
background-size: 60px 60px;
animation: bga-21-run 1.4s linear infinite;
}
@keyframes bga-21-run {
to {
background-position: 0 60px;
}
}
.bga-21__horizon {
position: absolute;
inset-inline: 0;
bottom: 43.4%;
height: 2px;
background: linear-gradient(90deg,transparent,var(--bga-21-hot),oklch(1 0 0),var(--bga-21-line),transparent);
box-shadow: 0 0 26px 5px color-mix(in oklab,var(--bga-21-line) 55%,transparent);
}
.bga-21__stage {
position: relative;
z-index: 5;
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
padding: clamp(22px,5vw,68px) clamp(22px,5vw,68px) 46vh;
}
.bga-21__copy {
width: min(100%,660px);
display: grid;
gap: 14px;
justify-items: center;
text-align: center;
}
.bga-21__eyebrow {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
letter-spacing: .24em;
text-transform: uppercase;
color: oklch(0.9 0.12 320);
}
.bga-21__title {
font-size: clamp(44px,13vw,142px);
font-weight: 800;
letter-spacing: .06em;
line-height: .9;
background: linear-gradient(180deg,oklch(0.99 0.03 90) 0 42%,var(--bga-21-hot) 52%,oklch(0.66 0.25 350) 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
filter: drop-shadow(0 0 26px color-mix(in oklab,var(--bga-21-line) 55%,transparent));
}
.bga-21__sub {
max-width: 48ch;
font-size: clamp(13px,1.3vw,16px);
line-height: 1.6;
color: oklch(0.95 0.02 320/.8);
text-wrap: pretty;
text-shadow: 0 1px 18px oklch(0.1 0.04 300/.9);
}
.bga-21__gauges {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
margin-top: 6px;
}
.bga-21__gauges span {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
padding: 7px 13px;
color: oklch(1 0 0/.7);
background: oklch(0.2 0.06 300/.45);
border: 1px solid color-mix(in oklab,var(--bga-21-line) 34%,transparent);
}
.bga-21__gauges b {
color: oklch(0.94 0.1 330);
}
@media (prefers-reduced-motion: reduce) {
.bga-21__floor,
.bga-21__sun,
.bga-21__stars {
animation: none;
}
}
```Here's a working CSS Background Animation from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Synthwave 3D Retro Grid
Source: https://codefronts.com/motion/css-background-animations/retro-cyberpunk-grid-synthwave-horizon/
The outrun horizon: a real 3D floor built with perspective and rotateX, scrolling toward you forever, under a slit-scanned neon sun and a chrome horizon glow. The infinite scroll is a single trick — translate the grid by exactly one cell in Z-space — and because the plane is genuinely rotated in 3D, the perspective convergence is correct rather than faked with a skew.
## HTML
```html
<section class="bga-21" aria-label="Synthwave 3D retro grid background demo">
<div class="bga-21__sky" aria-hidden="true"></div>
<div class="bga-21__stars" aria-hidden="true"></div>
<div class="bga-21__sun" aria-hidden="true"></div>
<div class="bga-21__haze" aria-hidden="true"></div>
<div class="bga-21__scene" aria-hidden="true">
<div class="bga-21__floor"></div>
</div>
<div class="bga-21__horizon" aria-hidden="true"></div>
<div class="bga-21__stage">
<div class="bga-21__copy">
<p class="bga-21__eyebrow">Perspective · rotateX(78deg) · 21</p>
<h1 class="bga-21__title">OUTRUN</h1>
<p class="bga-21__sub">A genuinely 3D plane, not a skew. The velocity gradient from near to far comes free with the projection — that is the part your eye reads as speed.</p>
<div class="bga-21__gauges">
<span>perspective <b>340px</b></span>
<span>cell <b>60px</b></span>
<span>loop <b>1.4s</b></span>
</div>
</div>
</div>
</section>
```
## CSS
```css
.bga-21,
.bga-21 *,
.bga-21 *::before,
.bga-21 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.bga-21 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
overflow: hidden;
isolation: isolate;
--bga-21-line: oklch(0.78 0.22 330);
--bga-21-glow: oklch(0.72 0.2 300);
--bga-21-hot: oklch(0.86 0.2 60);
background: oklch(0.1 0.04 292);
color: oklch(0.98 0.02 320);
font-family: ui-sans-serif,system-ui,-apple-system,'Segoe UI',sans-serif;
-webkit-font-smoothing: antialiased;
}
.bga-21__sky {
position: absolute;
inset: 0 0 42% 0;
background: linear-gradient(180deg,oklch(0.1 0.05 292) 0%,oklch(0.2 0.1 300) 44%,oklch(0.4 0.16 330) 78%,oklch(0.56 0.2 350) 100%);
}
.bga-21__stars {
position: absolute;
inset: 0 0 52% 0;
opacity: .6;
background-image: radial-gradient(oklch(1 0 0/.85) .9px,transparent 1.1px),radial-gradient(oklch(0.9 0.06 320/.6) .7px,transparent 1px);
background-size: 130px 130px,77px 77px;
background-position: 0 0,38px 52px;
animation: bga-21-tw 6s ease-in-out infinite alternate;
}
@keyframes bga-21-tw {
from {
opacity: .35;
}
to {
opacity: .75;
}
}
.bga-21__sun {
position: absolute;
left: 50%;
bottom: 41%;
translate: -50% 0;
width: min(46vmin,380px);
aspect-ratio: 1;
border-radius: 50%;
background: linear-gradient(180deg,var(--bga-21-hot) 0%,oklch(0.78 0.22 30) 42%,oklch(0.62 0.25 8) 74%,oklch(0.52 0.24 340) 100%);
-webkit-mask-image: linear-gradient(oklch(0 0 0) 0 46%,transparent 46%),repeating-linear-gradient(oklch(0 0 0) 0 9px,transparent 9px 15px);
mask-image: linear-gradient(oklch(0 0 0) 0 46%,transparent 46%),repeating-linear-gradient(oklch(0 0 0) 0 9px,transparent 9px 15px);
-webkit-mask-composite: source-over;
mask-composite: add;
filter: drop-shadow(0 0 70px color-mix(in oklab,var(--bga-21-hot) 55%,transparent));
animation: bga-21-pulse 7s ease-in-out infinite alternate;
}
@keyframes bga-21-pulse {
to {
filter: drop-shadow(0 0 110px color-mix(in oklab,var(--bga-21-hot) 75%,transparent));
}
}
.bga-21__haze {
position: absolute;
inset: 0 0 30% 0;
pointer-events: none;
mix-blend-mode: screen;
opacity: .5;
background: radial-gradient(70% 40% at 50% 100%,color-mix(in oklab,var(--bga-21-glow) 55%,transparent),transparent 72%);
}
.bga-21__scene {
position: absolute;
inset-inline: -30%;
inset-block-end: 0;
height: 44%;
perspective: 340px;
perspective-origin: 50% 0%;
-webkit-mask-image: linear-gradient(transparent,oklch(0 0 0) 16%);
mask-image: linear-gradient(transparent,oklch(0 0 0) 16%);
}
.bga-21__floor {
position: absolute;
inset: 0;
transform: rotateX(78deg);
transform-origin: 50% 0%;
background-color: oklch(0.12 0.05 300);
background-image: linear-gradient(color-mix(in oklab,var(--bga-21-line) 90%,transparent) 2px,transparent 2px), linear-gradient(90deg,color-mix(in oklab,var(--bga-21-line) 90%,transparent) 2px,transparent 2px);
background-size: 60px 60px;
animation: bga-21-run 1.4s linear infinite;
}
@keyframes bga-21-run {
to {
background-position: 0 60px;
}
}
.bga-21__horizon {
position: absolute;
inset-inline: 0;
bottom: 43.4%;
height: 2px;
background: linear-gradient(90deg,transparent,var(--bga-21-hot),oklch(1 0 0),var(--bga-21-line),transparent);
box-shadow: 0 0 26px 5px color-mix(in oklab,var(--bga-21-line) 55%,transparent);
}
.bga-21__stage {
position: relative;
z-index: 5;
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
padding: clamp(22px,5vw,68px) clamp(22px,5vw,68px) 46vh;
}
.bga-21__copy {
width: min(100%,660px);
display: grid;
gap: 14px;
justify-items: center;
text-align: center;
}
.bga-21__eyebrow {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
letter-spacing: .24em;
text-transform: uppercase;
color: oklch(0.9 0.12 320);
}
.bga-21__title {
font-size: clamp(44px,13vw,142px);
font-weight: 800;
letter-spacing: .06em;
line-height: .9;
background: linear-gradient(180deg,oklch(0.99 0.03 90) 0 42%,var(--bga-21-hot) 52%,oklch(0.66 0.25 350) 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
filter: drop-shadow(0 0 26px color-mix(in oklab,var(--bga-21-line) 55%,transparent));
}
.bga-21__sub {
max-width: 48ch;
font-size: clamp(13px,1.3vw,16px);
line-height: 1.6;
color: oklch(0.95 0.02 320/.8);
text-wrap: pretty;
text-shadow: 0 1px 18px oklch(0.1 0.04 300/.9);
}
.bga-21__gauges {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
margin-top: 6px;
}
.bga-21__gauges span {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
padding: 7px 13px;
color: oklch(1 0 0/.7);
background: oklch(0.2 0.06 300/.45);
border: 1px solid color-mix(in oklab,var(--bga-21-line) 34%,transparent);
}
.bga-21__gauges b {
color: oklch(0.94 0.1 330);
}
@media (prefers-reduced-motion: reduce) {
.bga-21__floor,
.bga-21__sun,
.bga-21__stars {
animation: none;
}
}
```How this works
Two declarations create the floor. The parent supplies the camera, the child is laid flat:
.scene { perspective: 340px; perspective-origin: 50% 0%; }
.floor {
transform: rotateX(78deg);
transform-origin: 50% 0%;
background-image:
linear-gradient(var(--line) 2px, transparent 2px),
linear-gradient(90deg, var(--line) 2px, transparent 2px);
background-size: 60px 60px;
animation: run 1.4s linear infinite;
}
@keyframes run { to { background-position: 0 60px; } }Moving the background by exactly one cell (60px) per loop makes the motion perfectly seamless — the same tile-multiple rule as the dot grid in demo 03, but now it is being viewed through a perspective transform, so the cells nearest the camera appear to accelerate while distant ones crawl. That velocity gradient is what your eye reads as speed, and you get it for free from the projection.
perspective-origin: 50% 0% places the vanishing point at the top edge of the floor element, which is where the horizon line must sit for the illusion to hold.
The sun is one circle with a hard-stop repeating gradient masked over its lower half — the slit-scan look — plus a large soft glow behind it:
background: linear-gradient(oklch(.86 .18 88), oklch(.66 .25 12));
mask-image: repeating-linear-gradient(#000 0 8px, transparent 8px 13px);Above the horizon, a low-frequency star layer and a chromatic haze finish it. Nothing here uses an image or a script; the entire scene is six elements.
Make it yours
- Speed is
animation-durationalone: 2.4s is a cruise, 0.7s is a chase. Keep the cell size fixed so the perceived scale does not change with speed. - Add a second floor plane above the camera (
rotateX(-78deg)) for a ceiling grid — instantly reads as a tunnel rather than a road. - Give the grid lines a
drop-shadowin the accent hue for neon bloom; keep the blur small or the far cells smear into a glow. - Fade the horizon with a
mask-imageon the floor so distant lines dissolve rather than stacking into a solid band — essential at low perspective values.
Gotchas — read before shipping
- Without a mask or fade, distant grid lines converge into a solid bright band at the horizon. Always fade the far edge.
- 3D transforms create a new containing block and their own compositing layer; nested transforms inside the floor will behave unexpectedly.
- Very low
perspectivevalues (under about 200px) produce extreme distortion and aliasing on the nearest cells. 300-500px is the usable range. - Animating
background-positionon a 3D-transformed element repaints a large area — keep the floor to roughly half the viewport height. - Fast repeating high-contrast neon patterns are a photosensitivity risk; cap the speed and honour
prefers-reduced-motion.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 114+ | 17.5+ | 121+ | 114+ |
Floor set by oklch(), color-mix(), text-wrap as this demo is written. The core technique itself goes back further — Chrome any modern.
CSS 3D transforms, perspective, repeating gradients and mask-image (with -webkit- prefix for older Safari/Chromium) are supported everywhere current. No JavaScript, no images, six elements.