Home / Snippets / Layout /

Duotone image with background-blend

Create a duotone image effect using CSS background-blend-mode — replace the image's colors with two custom hues using only CSS, no image editing required.

Original
Cool duotone
Warm duotone
Widely Supported
colorno-js

Quick implementation

/* HTML: <div class="duotone" style="--color1: oklch(0.72 0.19 265); --color2: oklch(0.4 0.18 320);"></div> */
.duotone {
  background-image:
    linear-gradient(to bottom, var(--color1), var(--color2)),
    url('your-photo.jpg'); /* replace with your image */
  background-blend-mode: multiply;
  background-size: cover;
  background-position: center;
}

Prompt this to your LLM

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

You are a senior frontend engineer with expertise in CSS blend modes and
image effects.

Goal: Implement a duotone image effect using CSS background-blend-mode:
multiply — no SVG filter, no image editing, no JavaScript. The effect
maps image tones to two configurable CSS colors via custom properties.

Technical constraints:
- The duotone element is a div (not an <img>) that uses background-image
  with two layers: a linear-gradient on top and url('photo.jpg') on
  the bottom.
- Set background-blend-mode: multiply on the element. Multiply blending
  multiplies the pixel values of the two layers — white (1) preserves the
  underlying layer's color, black (0) produces black.
- Set background-size: cover and background-position: center on both
  layers so they fill the element consistently.
- Expose the two tone colors as CSS custom properties --color1 (light
  tone, applied at the gradient top) and --color2 (dark tone, gradient
  bottom) so instances can override colors with inline styles.
- Use oklch() for all color values — no hex or rgba.
- The element needs a defined width and height (or aspect-ratio) since it
  is a div, not a replaced element.

Framework variant (pick one):
A) Vanilla CSS — a .duotone utility class that reads --color1 and
   --color2 custom properties. Set default values with @property or
   in a :root block so the class is self-contained.
B) React component — a <Duotone> that accepts src (string), color1
   (string), color2 (string), width (string), and height (string) props
   and applies the background-image inline style. Include an aria-hidden
   wrapper so the image is treated as decorative; wrap a visually-hidden
   <img> inside for accessibility if the image conveys meaning.

Edge cases to handle:
- The duotone approximation is not identical to a true Photoshop duotone
  because CSS multiply blending is not the same as a grayscale + colorize
  channel operation. For a closer result, add a grayscale() filter to the
  element before the blend: filter: grayscale(1) — then remove it; the
  gradient multiply handles the tone mapping.
- If the image has transparent areas, background-blend-mode blends against
  the element's background-color, not the page background. Set
  background-color: transparent or a specific fallback color.
- Overlaid text: duotone can significantly darken or alter image contrast.
  Always test text contrast against the blended result, not the original
  image. Consider adding a semi-transparent overlay layer above the
  gradient for improved text legibility.
- Accessibility: if the element conveys meaningful content, use an <img>
  with alt text rather than a background-image div, then apply the
  duotone via mix-blend-mode on a pseudo-element overlay.

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

How background-blend-mode: multiply creates a duotone

Duotone effects replace an image's tonal range with two colors: a highlight color (applied to the lightest areas) and a shadow color (applied to the darkest areas). Traditionally this required Photoshop's Duotone mode or a complex SVG feColorMatrix filter chain. background-blend-mode: multiply gives a close approximation using nothing but CSS.

The technique works because of how multiply blending operates: it multiplies the pixel values of two layers, where values are normalized between 0 and 1. Multiplying any value by white (1, 1, 1) returns the original value unchanged — the gradient's highlight color shines through in the image's bright areas. Multiplying any value by black (0, 0, 0) produces black — dark areas in the image become the gradient's dark tone. The gradient maps continuously from the highlight color to the shadow color, and the image's luminosity modulates that mapping.

The result is not identical to a true duotone (which operates on a grayscale channel), but it is close enough for most decorative uses. For a tighter approximation, add filter: grayscale(1) to the element before the gradient multiply — this removes the image's original hue information and lets the gradient fully control the tone mapping.

Custom properties for configurable tones

Using --color1 and --color2 as custom properties makes each duotone instance independently configurable via inline styles or a parent class. The base .duotone class provides default values; any instance can override them: <div class="duotone" style="--color1: oklch(0.78 0.18 60); --color2: oklch(0.45 0.2 20);"> switches the tone palette from cool purple to warm amber-red without a new class.

If you use the @property at-rule to register the custom properties, the browser gains type information about them — particularly useful if you want to animate the tone colors. Registered custom properties with syntax: "<color>" can be transitioned smoothly, allowing a hover effect that shifts the duotone palette from one hue pair to another.

Accessibility and performance

If the image conveys meaningful content (a photo of a person, a product image, a map), use an <img> element with alt text and apply the duotone via a ::after pseudo-element overlay with mix-blend-mode: multiply, rather than replacing the <img> with a background-image div. The <img> stays in the DOM with its accessible name intact. For purely decorative images, the background-image div approach is fine — set aria-hidden="true" on the div.

background-blend-mode is GPU-composited in most modern browsers and does not cause layout reflow. Test contrast: duotone effects that map toward dark tones can significantly reduce the readability of text overlaid on the image. Always check with a contrast analyzer using the blended result, not the original image colors. For improved text legibility, consider adding a third gradient layer above the duotone gradient that transitions to a semi-transparent dark overlay at the text position.