Using the CSS contrast() filter in WordPress themes

Hero sections are where this usually goes wrong. The standard advice in the WordPress ecosystem is to drop a semi-transparent black overlay behind the text, and it flattens the design. When I build WooCommerce themes for clients, the CSS contrast() filter is what I reach for instead, because it fixes readability without turning the header into a muddy mess. The case that keeps coming back is the pastel photo: a client uploads something soft and dreamy, the white text on top disappears into it, and the page reads as broken.

Darkening the whole image is the blunt version of the fix. What you want is control over the range between light and dark pixels, and that is what the CSS contrast() filter gives you. It runs on RGB math, multiplying each channel to expand or compress the color spectrum, so the result is predictable in a way an overlay never is.

CSS contrast() filter syntax

The syntax itself is simple. Where it gets awkward is stacking it with other filters like brightness(). You can write the value as a percentage or a decimal, with 100% as the baseline. Anything above that sharpens the separation between light and dark, and anything below pulls the colors toward a middle gray.

/* Senior Dev Tip: Always combine contrast with brightness for hero images */
.hero-image {
  filter: contrast(80%) brightness(70%);
}

/* For product card interactions */
.product-card:hover img {
  filter: contrast(125%);
  transition: filter 0.3s ease-in-out;
}

Unlike plain opacity, this keeps the blacks deep while it flattens the highlights. That matters in themes where you have no control over the quality of the source image, which is most of them once a client starts uploading. If text contrast is the specific part giving you trouble, I wrote up a different approach in my guide to CSS contrast-color().

The washed out product card

A boutique jewelry site I took on last year had high-key product photography, nearly white, sitting on a white background. Hovering a card did nothing; the images sat there flat. I rewrote the hover state with a small bump of the CSS contrast() filter to 125%, and the diamonds picked up their sparkle again while the shadows came back into definition. Five minutes of work, and it read like the photos had been retouched.

Be careful if you are layering this on top of heavy JavaScript animation, because the CSS transitions can end up fighting each other. Filters are expensive for the browser’s paint engine. Keep the transitions narrow and do not put one on every element of a page that has hundreds of nodes.

contrast() vs. contrast-color()

These two get mixed up constantly, and they do completely different jobs. contrast() is a filter: it changes how the pixels are rendered. contrast-color() is a color generator, and it picks the text color for you, usually black or white, based on the background. Per the MDN CSS contrast filter documentation, the filter function has much better support in legacy browsers, which is why it is still the one I ship in production themes.

If CSS filter work is eating your development hours, I can take it off your plate. I have been wrestling with WordPress since the 4.x days.

The final refactor

Do not just drop a filter on the main <img> tag and call it done. If what you need is the background behind a navigation bar to dull down while the text stays sharp, that is a job for backdrop-filter, which also avoids the ghostly blurred edges the older methods leave behind. Then check your paint times before you ship.

author avatar
Ahmad Wael
I'm a WordPress and WooCommerce developer with 15+ years of experience building custom e-commerce solutions and plugins. I specialize in PHP development, following WordPress coding standards to deliver clean, maintainable code. Currently, I'm exploring AI and e-commerce by building multi-agent systems and SaaS products that integrate technologies like Google Gemini API with WordPress platforms, approaching every project with a commitment to performance, security, and exceptional user experience.

Leave a Comment