Web Designguide12 min read

Duotone Heroes With CSS Blend Modes

Linear-gradient overlays plus multiply and screen. OKLCH duotone without Photoshop exports.

The hero needed duotone photography—brand navy into warm coral—and the design handoff was a folder of Photoshop exports at six breakpoints. Every copy tweak resized the text. Every rebrand replaced the PSD. The images looked sharp. The maintenance cost did not. Engineering inherited raster files as if color were baked into pixels instead of declared in tokens.

Duotone effects are color math: constrain a photograph to two dominant hues while preserving luminance structure. Photoshop’s gradient map does this in a layer stack. CSS does it with mix-blend-mode, background-blend-mode, and a linear-gradient overlay—no extra HTTP requests, no raster re-exports when gradient stops change. Hero sections usually prefer background-blend-mode because text and buttons stay children with normal blending, not mixed into the photo.

This guide covers blend mode mechanics, three duotone recipes, responsive structure, and accessibility for text sitting on blended photos. The goal is token-driven heroes that rebrand when custom property stops change, not when someone opens a PSD from 2019.

How blend modes combine layers

Blend modes define how a source layer combines with a backdrop, what is already composited beneath. For each pixel, the mode runs channel math. Multiply darkens. Screen lightens. Overlay does both by threshold. Color preserves backdrop luminance while taking source hue and saturation. Duotone workflows start with a full-color or grayscale photo, apply a mode that maps luminance to a two-color ramp, and optionally add a scrim for contrast under text.

CSS offers two attachment points. Mix-blend-mode makes the entire element blend with content behind it in the stacking context. Background-blend-mode blends multiple background layers on the same element with each other. Hero sections almost always want the second option so headlines remain legible children rather than participants in the blend.

Stacking contexts break expectations. Parent elements with opacity below 1, filter, or transform create new isolation contexts. Mix-blend-mode on a child then blends only within that context, not into the hero photo you thought sat behind it. Test Safari and Firefox, not only Chrome. Performance favors background-blend-mode on a static hero over full-viewport fixed layers with mix-blend-mode on mobile, where compositing cost accumulates with parallax.

Hard-light and overlay modes sit between multiply and screen in aggression. Hard-light applies screen or multiply depending on source brightness, producing harsher duotone separation that editorial layouts sometimes want. Overlay is softer on skin tones and sky gradients. Recipe choice should follow photography, not habit. Team photos with faces often prefer color blend to preserve luminance structure in highlights. Architectural shots with flat sky tolerate multiply because the midtone range is narrower. Product photography on white seamless may need screen on a dark page background so shadows retain detail. None of these choices are permanent. Because stops live in custom properties, art direction can A/B recipes in staging without re-exporting assets.

Three recipes for web heroes

The classic recipe desaturates conceptually through blend math: overlay a two-stop gradient on the photograph and use multiply or overlay to merge. Multiply darkens. Light areas pick up the gradient’s warm end. Dark areas absorb both layers and deepen. It works best on photos with strong luminance range. Screen inverts multiply logic for dark-page heroes where you want highlights to carry brand color. A dark base background color prevents multiply from crushing shadow detail entirely.

.hero-duotone {
  background-image:
    linear-gradient(
      to right,
      oklch(0.35 0.12 260),
      oklch(0.55 0.16 35)
    ),
    url('/images/team.jpg');
  background-size: cover;
  background-position: center;
  background-blend-mode: multiply;
  background-color: oklch(0.92 0.02 260);
}

.hero-duotone--dark {
  background-color: oklch(0.18 0.03 260);
  background-blend-mode: screen;
}

Photoshop gradient maps assign a dark-to-light ramp across luminance. CSS approximates with a single gradient at an angle and color or luminosity blend. Color blend mode keeps photo detail while chroma comes entirely from the gradient. That is the closest web analog to a true gradient map. Hue or saturation modes produce subtler shifts when you want less aggressive stylization.

The gradient-map simulation uses the same two-layer stack with background-blend-mode set to color at an angled linear-gradient from deep violet to warm amber over architecture photography. Color keeps luminance from the photo while chroma comes entirely from the ramp. Hue or saturation modes produce subtler shifts when full gradient-map aggression is too strong for the subject.

sRGB gradient stops mud through gray when hues oppose. Interpolate duotone stops in OKLCH so the coral-to-blue path stays vivid through midtones instead of desaturating through brown-gray.

.hero-oklch {
  background-image:
    linear-gradient(
      in oklch 135deg,
      oklch(0.32 0.14 264),
      oklch(0.58 0.17 28)
    ),
    url('/images/product.jpg');
  background-size: cover;
  background-blend-mode: color;
}

Structure matters as much as recipe. Blend modes affect backgrounds on the hero container, not automatically its children. A content wrapper sits above with position relative and z-index. Do not put mix-blend-mode on the text container unless you intend headlines to blend into the photo. When duotone crushes midtone contrast, add a semi-transparent scrim pseudo-element between photo and gradient, or split visual and content layers so a child div carries background-image and blend-mode while text stays sibling.

.hero-duotone {
  position: relative;
  min-height: 60vh;
  display: grid;
  place-items: center;
}

.hero-duotone::before {
  content: '';
  position: absolute;
  inset: 0;
  background: oklch(0.15 0.02 260 / 0.45);
  z-index: 0;
  pointer-events: none;
}

.hero-duotone__content {
  position: relative;
  z-index: 1;
  color: oklch(0.98 0.01 260);
  text-align: center;
  padding: 2rem;
}

Progressive enhancement for older browsers shows the photograph without blend when background-blend-mode is unsupported. Wrap enhanced rules in @supports background-blend-mode multiply so fallback is solid brand color under the photo. A desaturated CDN variant optional on the fallback URL helps flat overlay still read as stylized.

When pseudo-element scrims block background-blend-mode on the parent, split structure wins. A child div carries background-image and blend-mode absolutely positioned behind content. The header stays outside that subtree with its own solid fill so mix-blend-mode never bleeds into navigation. Feature sections sometimes use mix-blend-mode luminosity on an image while a wrapper supplies brand gradient. The image luminance shapes the blend. The wrapper supplies chroma. Changing tokens updates art without PNG re-export. That pattern is not a hero pattern, but it shares the same maintenance argument: color belongs in stylesheets, not in flattened pixels.

Text, scrims, and contrast on blended photography

Duotone does not exempt headlines from contrast requirements. WCAG 2.2 SC 1.4.3 still applies to text over images. Measure foreground against the rendered pixel behind the text, not the pre-blend photo. Practical sequence: pick duotone stops, add scrim if needed, run headline and body colors through a contrast checker using sampled composite color or a conservative assumption of the darkest midtone in the gradient. Sampling matters because a duotone hero is not one background color. It is a field of colors. Conservative teams sample five points across the headline bounding box and require every sample to pass. Aggressive teams sample worst-case midtone from the gradient ramp mathematically and add scrim until that synthetic worst case passes. Either beats assuming white text is fine because the pre-blend photo looked dark.

Text-shadow does not reliably improve contrast ratio in WCAG calculations. Scrim or solid backing plate works. Opaque text colors tested against scrim plus duotone worst case beat assuming white defaults. Do not rely on color alone for CTA hierarchy. Weight, size, and position reinforce meaning per SC 1.4.1. Prefers-contrast more increases scrim opacity and text lightness when users request higher contrast.

When duotone applies to isolated elements over variable page backgrounds rather than self-contained heroes, mix-blend-mode on the element blends with whatever sits behind. A wrapper supplies gradient color while the image contributes luminance. Changing brand tokens updates feature art without re-exporting PNGs. Watch stacking contexts again.

Animating duotone on hover or scroll recomputes when gradient stops change. Transition background-image on stops, not background-blend-mode, which is non-animatable in most engines. Respect prefers-reduced-motion by disabling transition. Heavy scroll-linked blend on mobile can jank compositing.

Debugging blend surprises: gradient hides the photo when top layer is opaque or mode is wrong—try color, multiply, or reduce gradient alpha. Colors differ per browser when OKLCH stops exceed sRGB on wide-gamut displays. Text looks washed out when duotone lifted midtones toward light coral and white headline fails contrast. Blend affects chrome when mix-blend-mode on a header over hero bleeds into navigation; contain blend to a visual child layer.

Case study: six breakpoints of Photoshop exports

A B2B analytics startup launched a marketing site whose hero was the product story: team in office, data on screen, brand navy to coral duotone tying photography to the logo gradient. Design delivered six Photoshop exports at breakpoints from mobile to ultrawide, each with slightly different crop and slightly different gradient angle because the art director had tuned each comp by hand. The site looked excellent in the launch review. Stakeholders praised consistency across breakpoints, not realizing consistency was enforced by six separate rasters that could drift the moment someone saved the wrong layer. Three months later marketing wanted one headline word changed. The hero text reflow pushed layout enough that the mobile crop felt off. Design re-exported six files. Engineering updated srcset. Total time: four hours for a word change. Nobody in that meeting used the word maintenance. They used the word polish, which is how expensive pipelines hide in plain sight.

The rebrand four months after that was worse. Brand shifted coral warmer and navy deeper. New PSD folder. Six exports again. A designer on vacation left notes that did not match the file names. Engineering shipped a hero where tablet used an old coral because the wrong asset sat in the CDN folder. Nobody noticed until the CEO opened the site on an iPad.

The rewrite started from a question: what if the photograph were one file and the duotone were CSS custom properties? They picked the color recipe because it preserved facial detail in team photography better than multiply on their navy-forward palette. One WebP photograph at high resolution, cover sizing, gradient stops on --hero-duotone-start and --hero-duotone-end in OKLCH. Rebrand changed two variables in the token file. Every breakpoint updated simultaneously because cover scaling handled crop, not separate rasters.

Accessibility review came late in the original site and passed on a screenshot, not on computed pixels. The CSS hero forced a real test. White headline on the blended midtone failed against several sample points. They added a scrim pseudo-element at 45 percent opacity dark navy, re-tested with sampled composite, and passed AA with margin. They documented worst-case sample coordinates in the design system so future stop changes would trigger re-test.

Marketing asked for a subtle hover shift on the homepage hero. Gradient stop transition over four hundred milliseconds worked without new assets. Reduced motion query disabled it. Performance profiling on a mid-tier Android phone showed acceptable paint cost for a static hero; they declined scroll-linked parallax duotone after jank on a prototype. Will-change transform on blended children was tried briefly and rolled back after layer explosion on scroll. The team documented that decision in the starter template so the next hire would not rediscover the same dead end.

Engineering added a Storybook story for the hero with controls bound to duotone start and end tokens so design could preview stops without requesting deploys. That story became the approval surface for cobrand pages. Accessibility paired the story with a contrast note field listing sampled pixel coordinates from the worst headline region. The process looked heavier than dropping PNGs into a folder. It was lighter every time the folder would have reopened.

The Photoshop archive still exists in a shared drive for print campaigns. The web hero has not been re-exported in eighteen months. Copy changes are HTML. Color changes are tokens. The art director initially missed the per-breakpoint craft control. She came around when a campaign landing page reused the same hero pattern with different stops for a partner cobrand without a design sprint.

One regression almost shipped when a new sticky header used mix-blend-mode on its logo mark over the hero. The mark bled into navigation chrome on scroll. Containing blend to .hero__visual and giving the header a solid background-color in a separate stacking context fixed it. That class of bug does not show in static design comps. It appears when scroll and fixed positioning enter the story.

Image choice still matters. Low-contrast photography produces muddy duotone regardless of blend mode sophistication. High-frequency noise in dark shadows can band when multiply deepens both layers. Providing one well-exposed base photograph remains cheaper than fixing bad source material in CSS. CDN delivery should serve resolution variants through picture or image-set for sharpness, not color variants. Color is CSS job. Resolution is asset job. Keeping that boundary clear prevented the team from recreating the six-export nightmare in a different shape.

The lesson the team keeps citing is that duotone in CSS trades export flexibility for token-driven maintainability. That trade is correct when brand colors change faster than photography. Background-blend-mode color with OKLCH interpolation, scrim-backed text contrast verified in a contrast checker, child content outside the blend stack, and progressive enhancement for unsupported engines form the default pattern in their starter template. Blend modes are live theming. Change custom property stops on rebrand and every hero updates without opening Photoshop. The technique is not free—stacking contexts, contrast measurement, and motion preferences require discipline—but it is cheaper than six breakpoints of lies waiting in a folder.