How to target non-empty but invalid input elements with CSS

ยท

2 min read

Today a Twitter thread about forms by Arslan Khalid passed my timeline. The thread included a smart CSS rule that wasn't on my radar so far. It targets invalid but not empty (!) input elements. So, why is that cool? Let's take a step back...

Input elements support multiple HTML attributes that can enable input field validation. These attributes reach from defining an input element's type to enable email validation, pattern to only allow particular characters or required to mark the element as mandatory to fill.

You could have an email field like the following.

<!-- a required email address -->
<input type="email" placeholder="susi@example.com" required>

How could you show that the field element is invalid after someone interacted with the field? CSS offers the :invalid pseudo-class for that use case (at least theoretically).

input:invalid {
  border: 2px solid red;
}

The catch with this pseudo-class is that it matches input elements even when they're empty and/or the user didn't interact with them (which is a huge bummer). If you're relying on the :invalid pseudo-class to visualize validation state, your users' form editing experience starts with a bunch of red borders โ€“ not great! Unfortunately, the :empty pseudo-class isn't a great help either, because it targets elements without children. What could you use instead?

While not being the perfect solution either, Arslan's proposed CSS rule is better than purely relying on :invalid. It uses :invalid in combination with :not(:placeholder-shown). This way, elements only get marked as invalid when the placeholder is not shown (meaning that they're not empty anymore).

input:not(:placeholder-shown):invalid {
  border: 2px solid red;
}

If you wonder about browser support, :placeholder-shown is cross-browser supported these days.

Edited: Amy Kapers pointed out that it would be great to combine the selector with :focus to not any validation state while someone is still typing. I agree, that's a great idea. Thanks, Amy!

input:not(:placeholder-shown):not(:focus):invalid {
  border: 2px solid red;
}

Also, Koen Cornelis brought up the point that placeholders usually have more disadvantages than advantages. If you don't want to define placeholder values, you can still go with a space to keep the same CSS functionality.

<!-- a required email address with a placeholder space -->
<input type="email" placeholder=" " required>

Here's a quick CodePen to see the selector in action and also have a look at Aslan's thread on great forms. ๐Ÿ‘‹