TechEarl

Fixed (Parallax-Style) Background Images with background-attachment

How to pin a background image to the viewport so content scrolls over it (the cheap parallax look), the full cover/center/fixed recipe, why background-attachment is not background-position, and the iOS Safari caveat that breaks it on mobile.

Ishan Karunaratne⏱️ 6 min readUpdated
Share thisCopied
Pin a background image to the viewport with background-attachment fixed for a parallax-style hero, plus the full cover/center recipe and the iOS Safari mobile fallback.

To keep a background image pinned to the viewport while the content scrolls over it (the cheap parallax look), set background-attachment: fixed. The background stays put; everything else moves. That is the whole trick:

css
.hero {
  background-image: url("/images/mountains.jpg");
  background-attachment: fixed;
}

background-attachment: fixed positions the image relative to the viewport instead of the element. As you scroll, the element moves but the image does not, so content appears to slide over a stationary photo. It is not real parallax (nothing moves at a different rate), but it reads as parallax for one line of CSS. The default is scroll, where the image is fixed to the element and scrolls away with it.

The full full-bleed recipe

A bare fixed rarely looks right on its own. For a hero that fills the viewport and covers cleanly at any size, add three more properties: background-size: cover so the image scales to fill without distorting, background-position: center so the crop stays sensible, and background-repeat: no-repeat so you never see a tiled seam.

css
.hero {
  min-height: 100vh;
  background-image: url("/images/mountains.jpg");
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  background-attachment: fixed;
}

Or, collapsed into the background shorthand (note size comes after position, separated by a slash):

css
.hero {
  min-height: 100vh;
  background: url("/images/mountains.jpg") center/cover no-repeat fixed;
}

Both are identical. The shorthand silently resets any background property you leave out to its initial value, so I keep the longhand while iterating and collapse to the shorthand once the values settle.

background-attachment is not background-position

This is the one thing the old comment threads on this topic always got wrong. Two different properties do two different jobs:

  • background-attachment controls scroll behavior: does the image move with the element, or stay fixed to the viewport. Values: scroll, fixed, local.
  • background-position controls placement: where the image sits in its painting area (center, top left, 50% 25%).

There is no background-position: fixed. Write that and the browser drops the invalid declaration, which is exactly why people who reached for it saw "nothing happened" and concluded fixed backgrounds were broken. The value you want is background-attachment: fixed: position decides where, attachment decides whether it scrolls.

The mobile caveat (this is the important one)

Here is the part that bites everyone: background-attachment: fixed does not work on iOS Safari, and is unreliable across mobile browsers generally. Safari on iPhone and iPad ignores it on purpose, not as a bug, because repainting a viewport-fixed background every scroll frame was too expensive on mobile hardware. On affected browsers the background just behaves as scroll (and historically some Android versions painted it in jumpy ways).

So a hero that looks great on desktop quietly falls back to a normal scrolling background on a phone, which is usually fine. If it is not, the standard workaround is to drop background-attachment and instead put the image on a separate element you fix with position: fixed, then layer content above it:

css
.hero {
  position: relative;
  min-height: 100vh;
}

.hero::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background: url("/images/mountains.jpg") center/cover no-repeat;
}

A real position: fixed element is honored everywhere, including iOS Safari, so the pinned-background look survives on mobile. The trade-off: it sits behind all scrolling content, so this suits a single full-page backdrop rather than several stacked fixed-background sections.

If you would rather just turn the effect off on small screens, a media query is the least-effort fix:

css
@media (max-width: 768px) {
  .hero {
    background-attachment: scroll;
  }
}

That gives desktop the parallax-style hero and gives mobile a plain, well-behaved background instead of relying on the browser to silently do the right thing.

A note on performance and motion

Even where supported, fixed forces the browser to repaint the background as you scroll, which costs frames on large images or busy pages. Keep the source reasonably sized and let cover do the scaling. If you build genuine multi-speed parallax with JavaScript or transforms on top, respect users who asked for less motion:

css
@media (prefers-reduced-motion: reduce) {
  .hero {
    background-attachment: scroll;
  }
}

The fixed-background look is harmless enough that I do not always gate it behind prefers-reduced-motion, but any layered, multi-speed parallax should be.

FAQ

See also

  • Fixed vs sticky positioning in CSS: when to reach for position: fixed (the mobile workaround above) versus position: sticky, and how each behaves on scroll.
  • CSS filters: darken or blur a hero background with filter or a ::before overlay so overlaid text stays readable.
  • The CSS :has() selector: style a section based on what it contains, handy for toggling a fixed-background treatment by content.

Sources

Authoritative references this article was fact-checked against.

TagsCSSbackground-attachmentbackground-imageparallaxbackground-size coveriOS Safariresponsive design

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts

Regex Word Boundaries: \b, \B, and Lookaround Equivalents

Regex word boundaries (\b and \B) match positions between word and non-word characters with zero width. The full reference with engine differences, Unicode handling, lookaround alternatives, and worked examples for whole-word replace, search highlighting, and log parsing.

How to Count Unique Matches with grep, sort, and uniq

The grep -o 'pattern' file | sort | uniq -c | sort -rn pipeline is the classic log-analysis one-liner. Why sort must come before uniq, how each stage works, worked examples for top IPs and status codes, the awk one-pass alternative for huge files, and the BSD vs GNU notes.