Home / Snippets / Color & Theming /

Fixed background

A parallax-like depth effect using background-attachment: fixed with an oklch gradient — no JavaScript required.

Section one

The background stays put

Scroll down inside this box. The gradient behind these cards is fixed to the viewport — it doesn't move with the content.

Section two

Cards float over the gradient

Each translucent card reveals a different slice of the gradient as it scrolls past, creating a sense of depth without any JavaScript.

Section three

Pure CSS parallax

The technique uses background-attachment: fixed on the scrolling container, anchoring the gradient to the viewport coordinate space.

Section four

oklch gradients look great here

The perceptually-uniform oklch color space produces smooth, vibrant gradient transitions with no muddy mid-tones.

Section five

Lightweight & accessible

No images, no JavaScript, no layout shift. A single CSS property creates the entire effect.

Widely Supported
colorno-js

Quick implementation

.fixed-bg {
  background: linear-gradient(
    160deg,
    oklch(0.22 0.12 280),
    oklch(0.18 0.08 240),
    oklch(0.24 0.14 310),
    oklch(0.16 0.06 200)
  );
  background-attachment: fixed;
}

Prompt this to your LLM

Includes role, constraints, two framework variants, and edge cases to handle.

You are a senior frontend engineer implementing a parallax-like depth
effect using only CSS — no JavaScript, no scroll event listeners.

Goal: A scrollable page section where a gradient background stays fixed
to the viewport while content cards scroll over it, creating the illusion
of depth. Use background-attachment: fixed on the scrolling container.

Technical constraints:
- Apply background-attachment: fixed together with a linear-gradient()
  using oklch() color stops — no hex values, no images.
- The gradient is defined on the element that is scrolled (e.g., a full-
  height section or the body itself).
- Floating content cards should use a semi-transparent background
  (e.g., oklch(0.13 0.02 260 / 0.8)) so the gradient shows through.
- Use CSS custom properties for any repeated color values.
- Do not use JavaScript scroll handlers or IntersectionObserver.

Framework variant (pick one):
A) Vanilla CSS — a .hero-fixed-bg class on a <section> containing
   multiple .card children with translucent backgrounds.
B) React — a <FixedBgSection> component that wraps children and accepts
   a gradientAngle prop (defaults to 160deg) and an array of oklch
   color stops.

Edge cases to handle:
- iOS Safari ignores background-attachment: fixed on non-body elements
  inside overflow containers — document this limitation and provide a
  @supports-based fallback that sets background-attachment: scroll.
- On high-DPI displays the gradient may look banded — add
  background-size: 200% 200% as a subtle workaround.
- Ensure content inside the section remains readable at all scroll
  positions by using sufficient contrast on the card backgrounds.
- Avoid setting overflow: hidden on the element with the fixed
  background, as this creates a new stacking context that breaks the
  fixed positioning of the background.

Return CSS only (or the React component if variant B is chosen).

Why this creates depth without JavaScript

background-attachment: fixed instructs the browser to paint the background relative to the viewport, not relative to the element's own scroll position. As the user scrolls, the content moves but the gradient stays anchored to the screen — your eye reads this as two layers at different distances, the same visual principle used by classic parallax effects. No scroll listeners, no requestAnimationFrame, no layout thrashing: just a single CSS declaration.

Combined with oklch gradients, the effect gains another dimension. The oklch color space interpolates along a perceptually uniform path, so gradient transitions look smooth and vibrant rather than muddy. A diagonal linear-gradient(160deg, ...) means the visible slice of the gradient shifts both horizontally and vertically as the page scrolls, giving the background subtle movement that changes with scroll position.

Mobile limitations — iOS ignores it

The most important caveat: iOS Safari does not support background-attachment: fixed on non-body elements inside an overflow scroll container. Apple's rendering engine treats the property as scroll on those elements, so the parallax effect silently disappears on iPhone and iPad. The body element itself is partially supported in some iOS versions, but scrollable <div> containers are not.

The recommended pattern is a @supports query combined with a feature-detection check, or simply accepting the graceful degradation — on iOS the background scrolls with the content like a normal element, which is perfectly usable. You can also use @media (hover: hover) as a rough proxy for non-touch devices where the effect is reliable:

/* Fallback: scroll on touch/mobile */
.fixed-bg {
  background-attachment: scroll;
}

/* Fixed only on devices likely to support it */
@media (hover: hover) {
  .fixed-bg {
    background-attachment: fixed;
  }
}

Performance — main thread paint cost

Unlike transform and opacity — which run on the compositor thread — background-attachment: fixed forces the browser to repaint the background on every scroll frame on the main thread. This is because the background position must be recalculated relative to the viewport each time the scroll position changes, and painting happens on the main thread.

In practice, a gradient-only fixed background is lightweight enough that this rarely causes dropped frames on modern hardware. The concern grows with large, complex backgrounds or when many fixed-background elements appear on the same page. If scroll performance is critical, measure with Chrome DevTools' Performance panel before optimising — a simple oklch gradient will usually paint in under 1ms per frame.

Avoid combining background-attachment: fixed with filter, will-change, or transform on the same element or its ancestors: these create a new stacking context that breaks the fixed-viewport coordinate space and causes the background to revert to scroll behaviour. Keep the fixed-background element free of those properties and apply visual effects to child elements instead.