Jan's Development Microblog
Go back to blog overview
| Jan Valkenburg | CSS | Reading time: 4 min

The invalid :invalid CSS input state

A few weeks ago, I was busy with some fine-tuning for my microblog and got stuck on the invalid :invalid state using CSS. I will tell you how to fix this.

A few weeks ago, I was busy with some fine-tuning for my microblog. At some point, I was looking at my input fields on the website and wondering why they were showing a red underline by default while the form was still empty and not submitted. It was time to look for a way to fix the invalid :invalid state, which I found in the end. However, I still find it strange that input fields were marked as invalid by default when they were empty. In this blog post, I will share my thought process in finding the correct solution.

Let's start by looking at my initial code for the input field that only accepts email addresses. I wrote an input with the type "email" and styled it with some CSS that has a pseudo :invalid statement, showing a red underline when the form input is invalid. Upon loading the HTML page, the red underline was already showing without any user interaction. This makes no sense to me. I expected the red underline to appear only when the user has some interaction with the form. However, this behavior is, for some reason, the default behavior. I have included the code I was using below.

<input type="email" required placeholder="email”>
input{
    border: 0.135rem solid black;
    &:invalid {
        border-block-end: 0.135rem solid red;
    }
}

My first solution using :placeholder-shown

After a quick search on the internet, I found a way to address this issue. There is a pseudo-class that checks if a placeholder is displayed to the user. The name of this pseudo-class is :placeholder-shown. If we include this in our CSS selector, we can somewhat detect if the user has interacted with the input field. A downside of using this pseudo-class is that it doesn’t work properly with form validation on submission.

<input type="email" required placeholder="email”>
input{
    border: 0.135rem solid var(--input-border-color);
    &:not(:placeholder-shown):invalid {
        border-block-end: 1px solid var(--accent-color);
    }
}

The correct solution using :user-invalid

An even better solution is to use the pseudo-class :user-invalid, which mitigates the need for the :not(:placeholder-shown) selector. And it even works with the browser form validation on submit.

input{
    border: 0.135rem solid black;
    &:user-invalid {
        border-block-end: 0.135rem solid red;
    }
}

Final word

This is one of those cases where CSS statements can be confusing. Personally, I expected that the :invalid pseudo-class would only be triggered on form submission, but it wasn’t. The industry might have come to the same conclusion and fixed it by introducing a new pseudo-element, instead of fixing the way :invalid is working. They might have valid reasons, as they cannot break the web. However, in my opinion, by creating a new pseudo-selector, they make things only more complicated.

「人生は運のゲームではない。勝ちたいなら、一生懸命働け。」— Life is not a game of luck. If you wanna win, work hard. - Sora, No Game No Life