CSS invert() filter: How to invert colors in images and UI

- Andrés Cruz - ES En español

CSS invert() filter: How to invert colors in images and UI

As we have seen on multiple occasions, CSS has evolved to a point where we no longer rely on external tools like Photoshop or GIMP to apply interesting visual effects, including all kinds of CSS filters. In my case, one of the things that caught my attention the most when I started experimenting with CSS filters was how simple it is to invert colors directly from the browser with just a single line of code.

In this article, I explain what the invert() filter in CSS is, how it works internally, how to use it correctly, and several real examples, from the most basic ones to an advanced one featuring multiple interactive elements.

The invert() filter is a clear example of the power of modern CSS: simple, powerful, and with practical uses for both images and entire interfaces.

What is the CSS invert filter and what is it used for?

The invert() filter is a CSS function belonging to the CSS Filter Effects module, used to invert the colors of an HTML element. Unlike editing the image in an external program, this inversion happens in real-time within the browser without altering the original file.

When we apply filter: invert() to an image or any HTML element:

  • Light colors turn dark
  • Dark colors turn light
  • The process happens in real-time without modifying the original graphic asset

This makes it an ideal solution for:

  • Creating attractive visual effects without needing alternative images
  • Highlighting elements on hover
  • Simulating dark modes or alternative themes quickly
  • Altering visual states without duplicating graphic assets

How invert() works: RGB channel logic

The invert() filter inverts each RGB color channel independently by subtracting its current value from 255.

The invert filter, part of the CSS filter module, lets you invert an image's colors with just one rule. Its logic is as straightforward as it is elegant: if we have the color black, that is:

rgb(0, 0, 0);

The result after applying the filter will be pure white:

rgb(255, 255, 255);

And the exact same thing happens with intermediate shades. For example, if we have a light gray:

rgb(232, 230, 232);

The filter calculates the complement of each channel to yield a dark gray:

rgb(23, 25, 23);

In short, the filter subtracts the value of each channel separately from 255. For example:

255 - 232 = 23

The logic of subtracting from 255

Each color in the RGB model is composed of three channels with values ranging between 0 and 255:

  • Black → rgb(0, 0, 0)
  • White → rgb(255, 255, 255)

Let's take the black pixel rgb(0, 0, 0). When applying filter: invert(1), the browser calculates the complement of each channel:

255 - 0 = 255
255 - 0 = 255
255 - 0 = 255

Result: rgb(255, 255, 255) — pure white. Conversely, if we start from white, we get black.

The exact same thing happens with intermediate colors. Starting from the light gray rgb(232, 230, 232), the browser calculates:

255 - 232 = 23
255 - 230 = 25
255 - 232 = 23

Result:

rgb(23, 25, 23)

That is, the filter subtracts each color channel from 255, channel by channel.

What happens with intermediate values (25%, 50%, 75%)

The invert() filter doesn't have to be applied at 100%. We can use intermediate values, which is where the real formula used internally by the browser comes into play:

amount * (255 - value) + (1 - amount) * value

Where:

  • amount is the inversion value (from 0 to 1)
  • value is the original color channel value

This formula explains why:

  • invert(0%) → no visible changes
  • invert(50%) → the image leans toward neutral gray tones (the midpoint between original and inverted)
  • invert(100%) → total color inversion

In practice, using intermediate values allows for much more subtle and controlled effects, especially when combined with transition to create smooth animations.

How to use the invert filter in CSS

The syntax is very straightforward. The invert filter is applied via the CSS filter property on any HTML element — not just images:

img {
 filter: invert(value);
}

Allowed values: percentage vs decimal number

You can specify the inversion value in two equivalent ways:

filter: invert(0.6);
filter: invert(60%);

Both lines produce the exact same result. CSS accepts both decimal format (from 0 to 1) and percentage format (from 0% to 100%) for consistency with other filters in the module.

Common values:

  • invert(0) or invert(0%) → no effect, original image
  • invert(0.5) or invert(50%) → partial effect, gray tones
  • invert(1) or invert(100%) → total color inversion

Difference between invert(1) and invert(100%)

There is no functional difference between both notations. CSS allows both formats for convenience and consistency with other filters in the module like contrast(), brightness(), or opacity(). Use whichever you find most readable in your code.

Of course, there are many other filters available in CSS: grayscale(), blur(), sepia(), saturate(), opacity(), brightness(), contrast(), hue-rotate(), and drop-shadow(); but in this article, invert() is the main star. You can combine them in the same filter declaration separated by spaces.

Inverting the colors of an image with the invert filter

The most basic case consists of applying total inversion to all colors of an image:

img {
 filter: invert(100%);
}

image with 100% invert filter in CSS

Although we can use a lower percentage for a more subtle effect; for example, 66%:

image with 66% invert filter in CSS

Or 50%, where the image leans toward mid-tone grays (the balance point between the original and the inverted version):

image with 50% invert filter in CSS

Or 25% for a barely noticeable inversion:

image with 25% invert filter in CSS

In general, you can use any value between 0% and 100% depending on the visual effect you want to achieve.

Another very practical use is enabling the invert filter when hovering over the image, which we achieve by combining it with the :hover selector:

img:hover {
 filter: invert(100%);
}

image with invert filter active on CSS hover

And we can enhance it by adding a CSS transition to smooth out the state change, achieving a seamless animation instead of an abrupt jump:

image with invert filter and transition on CSS hover

In this example, we used filter: invert(100%), but it's up to you to decide what proportion to apply based on your design needs.

Combined use with CSS transitions

To prevent the effect from being harsh, adding a transition is essential. This tells the browser to smoothly interpolate between the initial state and the final state of the filter:

img {
 filter: invert(0%);
 transition: filter 0.5s ease-in-out;
}

img:hover {
 filter: invert(100%);
}

One of the most appealing uses of the invert() filter is triggering it when hovering over an element. Combining :hover and transition creates an immediate, elegant visual experience that highlights the image without having to duplicate assets.

Smoothing the effect with transition

img {
 filter: invert(0%);
 transition: filter 0.5s ease;
}
img:hover {
 filter: invert(100%);
}

This small detail makes a huge difference in user visual experience. Without the transition, the change feels like a flicker; with it, the inversion flows naturally.

Advanced example: inverting colors across multiple elements with visual focus

This third example is considerably more interesting than the previous ones. The idea is to create an effect where the hovered element stands out with its original colors and a slight scale-up, while the rest of the items in the group invert and scale down slightly, creating a clear visual hierarchy without needing extra images.

When placing the cursor over an item (hover), it scales to a slightly larger size and retains its original colors; meanwhile, unselected items receive the invert(100%) filter and shrink slightly via scale(0.95). The combination of both states —selected vs. unselected— creates the perception that the active item rises above the rest.

Here is the final result:

Augmented Reality with Vuforia

vuforia logo

Multiple borders on a container

promotional image for multiple borders post in css

Multiple animated backgrounds with CSS

promotional image for animated backgrounds post with css

The HTML5 Progress Bar Element

promotional image for HTML5 Progress Bar post

First steps with HTML5 SVG

promotional image for SVG HTML animated backgrounds post

Responsive YouTube videos: CSS and JavaScript

promotional image for responsive YouTube post

Let's start building it!

The HTML

The structure is completely standard: a <main> element acting as the main container, containing a series of <article> elements with the class item that serve as interactive cards:

    <main class="content">
        <article class="item">
            <a href="">
                <h6>Title</h6>
                <p>Text.</p>
            </a>
        </article>
        <article class="item">
            <!-- More articles -->
        </article>
    </main>

The CSS

Here is the core of the effect. The CSS defines the visual model for the items, the hover rules for the active element, and the blur class that JavaScript dynamically assigns to unselected elements.

First, the main container: no more than 500px wide, centered, with a gray background serving as a canvas:

.content{
 max-width:500px;
 width:80%;
 height:auto;
 padding:20px;
 background:#CCC;
 margin: 0 auto;
}

Since we are using floating elements (float), we need to apply the clearfix trick so the container wraps them properly. We do this using the ::before and ::after pseudo-elements:

.content:before,
.content:after {
 content: "";
 display: block;
 clear: both;
}

The items are simple rectangular boxes separated from one another. The key lies in the transition property, which automatically smooths out all state changes: the box shadow, the scale, and, of course, the invert filter:

article.item{
 padding:5px;
 background:#FFF;
 width:150px;
 height:220px;
 margin:0 5px 5px 0;
 float: left;
 box-shadow: 1px 1px 10px rgba(0,0,0,0.4);
 transition:box-shadow 2s, transform 500ms, filter 500ms ease-in-out;
}

On hover over an item, we scale it up slightly and increase its shadow to enhance the feeling that it rises off the plane:

article.item:hover{
 transform: scale(1.05);
 box-shadow: 3px 3px 10px rgba(0,0,0,0.6);
}

And here is the class JavaScript will assign to unselected items: total color inversion and a subtle scale reduction via scale(0.95):

article.blur{
 filter: invert(100%);
 transform: scale(0.95);
}

The remaining styles are self-explanatory, so let's jump straight to JavaScript.

The JavaScript

Here is where JavaScript steps in to do what would be very difficult with pure CSS: applying a class to elements that do not have the cursor over them. CSS lacks a native selector for "all sibling elements except the one currently in :hover", so we solve it with a small script:

// Select all elements with the 'item' class
const items = document.querySelectorAll('.item');
items.forEach(item => {
    // Event when the mouse enters (mouseenter)
    item.addEventListener('mouseenter', () => {
        items.forEach(el => {
            if (el !== item) {
                el.classList.add('blur');
            }
        });
    });
    // Event when the mouse leaves (mouseleave)
    item.addEventListener('mouseleave', () => {
        items.forEach(el => {
            el.classList.remove('blur');
        });
    });
});

The script selects all elements with the item class and listens for two events on each: mouseenter (when the cursor enters) and mouseleave (when the cursor leaves). Upon entering, it adds the blur class —which contains filter: invert(100%)— to all items except the active one; upon leaving, it removes it from all items to restore the original state. Simple, effective, and dependency-free.

SVG invert filter: an advanced alternative

CSS is not the only way to invert colors in the browser. We can also accomplish this using SVG filters, specifically by utilizing the <feComponentTransfer> element with value tables that remap each color channel:

<svg xmlns="http://www.w3.org/2000/svg" style="display:none">
  <filter id="invert">
    <feComponentTransfer>
      <feFuncR type="table" tableValues="1 0"/>
      <feFuncG type="table" tableValues="1 0"/>
      <feFuncB type="table" tableValues="1 0"/>
    </feComponentTransfer>
  </filter>
</svg>

And it is applied by referencing the filter's id from CSS:

.filter {
 filter: url("#invert");
}

When to use SVG instead of CSS?

  • When you need compatibility with complex graphics pipelines or chained filters
  • When you are already working with SVG and want to reuse filters as centralized assets
  • When you need more granular control over individual color channels

For most day-to-day use cases, filter: invert() is more than enough. However, knowing the SVG alternative provides an extra resource for complex scenarios.

filter vs backdrop-filter: when to use each

It is important not to confuse these two properties, as they affect different areas of the document:

  • filter → affects the element itself and all its content
  • backdrop-filter → affects only the visual background behind the element

Example with backdrop-filter:

div.transbox {
 background-color: rgba(255, 255, 255, 0.4);
 backdrop-filter: invert(100%);
}

This is especially useful in modern glassmorphism-style designs, where a semi-transparent panel inverts the content sitting behind it without affecting its own contents.

Practical use cases and real-world applications of the CSS invert filter

Some scenarios where the invert() filter proves especially useful in real projects:

  • Highlighting images in photo galleries or portfolios
  • Creating eye-catching hover effects without needing alternate images or sprites
  • Simulating dark themes quickly without rewriting styles
  • Generating visual contrast without modifying original assets
  • Interactive interfaces with clear visual focus
  • Inverting dark icons or logos to adapt them to light backgrounds (or vice versa) using filter: brightness(0) invert(1), without touching the original file

Common mistakes and best practices

Avoid these common mistakes when working with filter: invert():

  • Applying invert(100%) to long passages of text, as it can significantly reduce readability depending on the background
  • Combining too many filters simultaneously without measuring performance impact (every filter involves a per-pixel real-time calculation)
  • Forgetting to add the transition property, causing state changes to feel abrupt and visually uncomfortable

Best practices:

  • Use intermediate values when you want subtle effects instead of full inversion
  • Always combine it with transition for a smoother, more professional user experience
  • Test the filter with different types of images before going to production; results vary greatly depending on the original color palette
  • To convert a black icon to white, use the combination filter: brightness(0) invert(1) instead of modifying the original SVG or PNG file

Frequently asked questions about invert() in CSS

  • Does invert() work only with images?
    • No, it works with any HTML element: <div>, <section>, <span>, buttons, forms… any renderable node in the DOM.
  • Can I animate invert()?
    • Yes, using the transition property for smooth interpolations, or @keyframes alongside animation for more complex cycles.
  • Is it supported in all modern browsers?
    • Yes, with full support for years in Chrome, Firefox, Safari, and Edge. You can check exact coverage on MDN Web Docs.
  • How do I convert a black icon to white with CSS?
    • Use the combination filter: brightness(0) invert(1). First, brightness(0) converts all pixels to pure black, and then invert(1) converts them to white.
  • Does the invert() filter affect the alpha channel (transparency)?
    • No. The alpha channel is not inverted; it only applies to the RGB channels, so the original image transparencies remain intact.

Conclusion

The invert() filter in CSS is a simple yet extremely powerful tool. In my experience, it's one of those effects that, when used properly, can completely transform the visual perception of an interface with minimal effort. What required editing the image in Photoshop a few years ago is now solved with filter: invert(1) and, if you want it to look elegant, a transition right next to it.

From basic image inversion to interactive effects across multiple elements, along with its combination with :hover, transition, and JavaScript, the possibilities are far broader than they appear at first glance.

Complete guide to CSS filter invert: invert colors in images and backgrounds to 100% or intermediate values, add hover transitions, and turn icons white.


Únete a la comunidad de desarrolladores que han decidido dejar de picar código y empezar a construir productos reales. Recibe mis mejores trucos de arquitectura cada semana:

I agree to receive announcements of interest about this Blog.