Styling Form Inputs in CSS With :required, :optional, :valid and :invalid

Introduction

When it comes to validating the content of input fields on the frontend, things are much easier now than they they used to be. We can use the :required, :optional, :valid and :invalid pseudo-classes coupled with HTML5 form validation attributes like required or pattern to create very visually engaging results. These pseudo-classes work for input, textarea and select elements.

Input pseudo-classes

Here’s an example of the pseudo-classes at work. Let’s start with this HTML markup:

<form action="#">   <input type="text" placeholder="First Name" required>   <input type="email" placeholder="Email" required>   <input type="text" placeholder="Nickname">   <input type="text" placeholder="Favorite pizza topping"> </form> 

And let’s apply the following styles:

input {   border: 2px solid;   border-radius: 4px;   font-size: 1rem;   margin: 0.25rem;   min-width: 125px;   padding: 0.5rem;   transition: background-color 0.5s ease-out; } input:optional {   border-color: gray; } input:required {   border-color: black; } input:invalid {   border-color: red; }  

Here’s the result:

See the Pen vYGeLYw by alligatorio (@alligatorio) on CodePen.

In the demo above, <input type="email" is an HTML5 input type that tells browsers to expect an email address. Because we are also using the required attribute, modern browsers will only set the input to :valid when a valid email is entered.

Adding the :focus pseudo-class

Let’s make things even more interesting by also styling according to the focus state and add a background image and color depending on the validity state and only when the input is in focus. We’ll use the same HTML markup.

Here’s our updated CSS:

input {   border: 2px solid;   border-radius: 4px;   font-size: 1rem;   margin: 0.25rem;   min-width: 125px;   padding: 0.5rem;   transition: border-color 0.5s ease-out; } input:optional {   border-color: gray; } input:required:valid {   border-color: green; } input:invalid {   border-color: red; } input:required:focus:valid {   background: url("https://assets.digitalocean.com/labs/icons/hand-thumbs-up.svg") no-repeat 95% 50% lightgreen;   background-size: 25px; } input:focus:invalid {   background: url("https://assets.digitalocean.com/labs/icons/exclamation-triangle-fill.svg") no-repeat 95% 50% lightsalmon;   background-size: 25px; } 

There are two key changes in the above CSS:

  1. input:required:valid applies a success state only to required inputs. Because technically, optional inputs are always valid.
  2. input:focus:valid' and 'input:focus:invalid apply to inputs only when they are focused.

And here’s the result:

See the Pen gOrGPxP by alligatorio (@alligatorio) on CodePen.

You could be tempted to add content instead using ::before or ::after on the input, but unfortunately that’s not possible on input elements. One trick would be to have a sibling span element that has content added depending on the validity of the input. Something like this:

input:focus:invalid + span::before { ... }.