GODRICH
#css-animation#button#loader#transition#scroll#dashboard#ui#design-trends

Mesh Gradient CSS, Grain Left In on Purpose

Mesh gradient CSS blends blurred color blobs across a dark canvas like an aurora, then adds film grain so it doesn't read as another AI-smooth template —

The one-page build below stitches all nine sections together. Scroll through it first and watch where the aurora gets dense and where it thins out.

Open the full page

Each mesh gradient css animation also appears on its own in the grid. The order isn't by popularity — it follows how a visitor actually reads a page: notice it, skim the features, check the price, trust the review, then click. Sculpt the same softness into volumes and the claymorphism landing kit appears; sharpen the edges instead and the neobrutalism landing sections stand on the opposite side.

Auto-plays · click a tile to jump to its section · all nine in one zip

01Hero

Three blobs drifting behind

Three blurred, colored blobs drift behind the headline at slightly different rhythms, overlapping as they move.

Drifting orb background hero
Open in a new tab

01 · Drifting orb background hero — Measured area 38.218% · moving frames 18/23Original by GODRICH · SCSS and React ship in the zip · See the password

blur(60px)translate+rotate그레인 오버레이

The card never moves

The card itself stays a fixed dark ink; the aurora is only ever light passing over it.

.hero-a__orb { position: absolute; width: 46%; aspect-ratio: 1;
  border-radius: 50%; filter: blur(28px); opacity: .85; }
.hero-a__orb--1 {
  background: radial-gradient($color 0%, $color 35%, transparent 68%);
  top: -10%; left: 4%;
  animation: driftA $duration steps(4, jump-none) infinite;
}
@keyframes driftA {
  0%   { transform: translate(0,0) rotate(0deg); }
  50%  { transform: translate(22px, 16px) rotate(20deg); }
  100% { transform: translate(0,0) rotate(0deg); }
}

02Navigation

A nearly transparent nav

The nav background is nearly transparent — just a thin sheet of ink-tinted glass underneath.

Transparent nav
Open in a new tab

02 · Transparent nav — Measured area 3.459% · moving frames 15/23Original by GODRICH · SCSS and React ship in the zip · See the password

backdrop-filter: blurtranslateXborder 반투명

Thirds, not guessed pixels

The pill marking the active item is sized by splitting the three links into equal thirds by percentage, not by a guessed pixel width.

.nav-a {
  background: rgba($stage-ink, .38);
  backdrop-filter: blur(10px);
  border: 1px solid rgba($stage-paper, .18);
}
.nav-a__pill {
  position: absolute; top: $sp-1; bottom: $sp-1; left: $sp-1;
  width: calc((100% - #{$sp-1 * 2}) / 3);
  border-radius: $r-pill; background: $color;
  animation: pillSlide $duration $ease-spring infinite;
}

03Card grid

A stripe only at the border

Only a ring at the card's border is cut out to show a three-color aurora stripe, and that stripe keeps flowing.

Gradient-border cards
Open in a new tab

03 · Gradient-border cards — Measured area 2.081% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

repeating-linear-gradient@keyframes sweepmask 이중 레이어

Why the eye stays on the edge

The card body underneath stays a fixed dark ink so the eye stays on the edge.

.card-a::before {
  content: ""; position: absolute; inset: 0;
  border-radius: $r-card; padding: $sp-1;
  background: repeating-linear-gradient(120deg,
    $color 0 26px, $stage-yellow 26px 52px,
    $stage-orange 52px 78px, transparent 78px 104px);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  animation: sweep $duration linear infinite;
}
@keyframes sweep { to { background-position: 208px 0; } }

04Feature list

One badge per feature

Each feature gets one round badge, and only the gradient inside that badge drifts side to side.

Gradient icon list
Open in a new tab

04 · Gradient icon list — Measured area 1.75% · moving frames 19/23Original by GODRICH · SCSS and React ship in the zip · See the password

background-size 200%background-positionradius pill

Why the whole row stays still

Moving the whole row would be distracting; one small drifting dot per line is enough to hold the eye.

.feat-a__icon {
  width: $sp-8; height: $sp-8; border-radius: $r-pill;
  background: linear-gradient(120deg, $color, $stage-yellow, $stage-orange, $color);
  background-size: 200% 100%;
  animation: iconFlow $duration ease-out infinite;
}
@keyframes iconFlow {
  0%   { background-position: 0% 50%; }
  50%  { background-position: 100% 50%; }
  100% { background-position: 0% 50%; }
}

05Pricing

Only the recommended plan breathes

Only the recommended plan gets an aurora gradient that brightens and dims like breathing, drifting slightly as it goes.

Gradient-highlighted plan
Open in a new tab

05 · Gradient-highlighted plan — Measured area 15.09% · moving frames 6/23Original by GODRICH · SCSS and React ship in the zip · See the password

radial-gradient 위치 이동opacity 호흡badge 고정

Contrast does the selling

The other plan stays a faint glass tile so the contrast does the selling.

.pricing-a__plan--pro::before {
  content: ""; position: absolute; inset: -20%;
  background:
    radial-gradient(circle at 30% 20%, rgba($color, .9), transparent 60%),
    radial-gradient(circle at 75% 80%, rgba($stage-orange, .85), transparent 60%);
  animation: breathe $duration steps(4, jump-none) infinite;
}
@keyframes breathe {
  0%, 100% { opacity: .3; transform: translate(0, 0); }
  50%      { opacity: 1;  transform: translate(-8%, 8%); }
}

06Testimonial

Blobs behind frosted glass

Two blurred aurora blobs drift slowly behind a frosted quote card.

Blurred-background quote
Open in a new tab

06 · Blurred-background quote — Measured area 45.86% · moving frames 6/23Original by GODRICH · SCSS and React ship in the zip · See the password

backdrop-filter: blurtranslate 루프border 반투명

Keeping the text crisp

The glass in front only gets a light blur so the text stays crisp while the color behind it stays soft.

.test-a__orb {
  position: absolute; width: 50%; aspect-ratio: 1;
  border-radius: 50%; filter: blur(20px);
}
.test-a__orb--1 {
  background: radial-gradient($subject-blue 0%, $subject-blue 45%, transparent 72%);
  top: -8%; left: -8%;
  animation: floatA $duration steps(4, jump-none) infinite;
}
.test-a__card {
  background: rgba($stage-ink, .22);
  backdrop-filter: blur(4px);
  border: 1px solid rgba($stage-paper, .18);
}

07CTA band

The band is the subject

A single full-width band carries a five-color aurora that keeps flowing, with a static grain layer on top so the screen reads like a printed poster.

Full-bleed aurora band
Open in a new tab

07 · Full-bleed aurora band — Measured area 36.246% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

background-position 애니메이션mix-blend-mode: overlayradius card

Third largest in the set

The whole band is the moving subject here, which puts its changed-pixel area among the largest in the set — third out of nine.

.cta-a {
  background: linear-gradient(120deg,
    $stage-ink, $color, $stage-orange, $stage-yellow, $stage-ink);
  background-size: 300% 100%;
  animation: flow $duration linear infinite;
}
@keyframes flow {
  0%   { background-position: 0% 50%; }
  100% { background-position: 100% 50%; }
}

08Footer

What makes a grainy gradient texture flicker

The film grain jumps to a new spot every frame instead of drifting smoothly, which is what actually reads as grain.

Grainy dark footer
Open in a new tab

08 · Grainy dark footer — Measured area 0.78% · moving frames 17/23Original by GODRICH · SCSS and React ship in the zip · See the password

steps() 그레인background-position 랜덤칸 배경 5%

Why steps() is the trick

Swapping the timing function for steps() is the whole trick — it's the only section where the grain itself is the animation.

.footer-a::before {
  content: ""; position: absolute; inset: 0;
  opacity: .45; mix-blend-mode: overlay;
  background-image:
    radial-gradient(rgba($color, .7) .6px, transparent .6px),
    radial-gradient(rgba($stage-ink, .7) .6px, transparent .6px);
  background-size: 3px 3px, 5px 5px;
  animation: grainJump $duration steps(6, jump-none) infinite;
}
@keyframes grainJump {
  0%   { background-position: 0 0, 0 0; }
  40%  { background-position: 5px -3px, -4px 6px; }
  100% { background-position: 0 0, 0 0; }
}

09Buttons & inputs

A gradient that waits for hover

The gradient inside the button only flows on hover, while the input skips a focus ring in favor of a constant, soft double box-shadow glow.

Gradient buttons, glow focus
Open in a new tab

09 · Gradient buttons, glow focus — Measured area 2.579% · moving frames 20/23Original by GODRICH · SCSS and React ship in the zip · See the password

background-position 호버box-shadow 이중 글로우radius control

Forcing it to play for capture

This is the only section triggered by hover, so the is-demo class forces it to play for capture.

.bi-a__btn {
  background: linear-gradient(120deg, $color, $stage-orange, $subject-blue, $color);
  background-size: 260% 100%;
}
.bi-a__btn:hover, .bi-a__btn:focus-visible {
  animation: shift $duration ease-out infinite;
}
.bi-a__input {
  box-shadow: 0 0 0 2px rgba($subject-blue, .55), 0 0 16px rgba($stage-orange, .45);
}

Custom tailwind.config

One config for the whole mesh gradient tailwind css set

Every color and radius that the nine sections reuse lives in this one config, so swapping the three aurora hues here carries across all of them — with two exceptions worth knowing before you touch it.

Two values that live elsewhere

Grain strength isn't in this file at all; it's a hardcoded opacity inside the one-page build's own <style> block. And the bg-aurora gradient utility repeats the same three hex values as literals rather than pulling from colors above it, so changing a color here won't retint that one utility on its own — you'd edit both places.

theme: {
  extend: {
    colors: {
      ink: "#17141a", cream: "#fff7e6", paper: "#ffffff",
      blue: "#2f6df6", yellow: "#ffd23f", orange: "#ff4d1f",
    },
    borderRadius: { tile: "16px", control: "12px", pill: "999px" },
    backgroundImage: {
      aurora: "linear-gradient(120deg, #17141a, #2f6df6, #ff4d1f, #ffd23f, #17141a)",
    },
  },
},

Vibe-coding prompt

A fragment is enough

Drop the config above into your project, then hand Claude Code or Cursor the fragment below — it's enough on its own. The full prompt lives in prompt.md inside the zip.

Background is ink only. Only where you want emphasis, overlap 2-3 blurred
(40-60px) blue/yellow/orange blobs so they bleed like an aurora. Never
paint the whole background with a gradient.
Layer faint grain (a radial-gradient dot pattern, opacity around .3) over
every dark surface. Build it with CSS only, never an image file.

What it produced

Applying this instruction to page/index.html and capturing the full page at a 1280px viewport produced the screenshot below. result

Where it breaks (the trap)

When the motion is too small to register

Moving the aurora blobs with a soft ease curve alone can make an animation invisible in practice — behind a heavy blur, the change between adjacent frames drops too low to register. The first version of section 05's recommended plan only swung opacity from .55 to 1, and the render measurement came back with an intensity of zero; only after widening the range to .3–1 and switching the timing to steps(4, jump-none) did it clear six moved frames. Reaching for semantic tags like <ul> or <figure>/<blockquote> without a second thought is another trap — their default browser margins survive unless you reset them, and at a 320px width that pushes the page taller than the viewport. Sections 04 and 06 hit exactly this, and the scrollbar only disappeared once margin: 0 was added explicitly. The zip holding all nine sections and the full one-page source needs the password u7eepwrp to open, and the button below starts the download.

Contrast on top of grain

The last trap is contrast. Desaturating the aurora blobs or switching to a light background makes text sitting on top of the grain lose contrast fast, so any text layer is best kept on the fixed dark ink background rather than directly on the gradient. The moment a grainy field has to become a flat image is already daily life in the marketing email templates, where backgrounds can only be images.

Accessibility

What reduced motion shows instead

Every animated section, including the gradient flow on the section 09 button, is wrapped in the media query defined by the MDN prefers-reduced-motion documentation, so a visitor with reduced motion turned on sees a static version of each one instead.

The older Safari fallback

Section 03's mask-composite border follows the MDN mask-composite documentation and keeps the older Safari fallback -webkit-mask-composite: xor alongside the standard value.

Text never sits on the gradient

Every quote and button label is set as white text on ink or ink text on cream, never directly on the moving gradient, so contrast holds up while the colors are shifting. If this particular look isn't the one you need, the design trends category collects the rest, and the about page explains what this site covers overall.

FAQ

Can mesh gradient CSS be built with CSS alone?

Yes — all nine sections in this set ship with zero lines of JavaScript. Even the hover response on the section 09 button runs entirely on the :hover and :focus-visible pseudo-classes; the only script involved is a short one that forces the animation to play while capturing the demo.

Does a grainy gradient css layer require an image file?

No. The grain here is two tiny radial-gradient dots layered and tiled at a 3–5px background size, so there isn't a single image request anywhere. Nudging the dot position every frame, as section 08's footer does, is enough to fake the flicker of real film grain with CSS alone.

Does this only work on a dark background?

The aurora blobs themselves still work on a light background, but all nine sections here default to a dark ink base. On a light background, lower the blob opacity and swap the grain's blend mode from overlay to multiply to keep a similar amount of texture. Push the same light toward metal and it becomes the mirrored surfaces of the Y2K chrome holographic landing.

Enter the archive password

The password is inside this article. You will find it as you read.