Form Validation with Only HTML5 and CSS

Published Jul 5, 2024

Written by: Abdulmumin Yaqeen


We do a lot of form validation in web development, starting from the frontend, to the backend where the data goes. Traditionally, In the frontend side, we rely heavily on JavaScript for this task. However, modern CSS, combined with HTML5 features, are more equipped than ever for client-side form validation.

In this article, we’ll look into how to validate forms using CSS.

HTML5 Form Attributes

In order for to validate our forms, these HTML attributes defines our requirements, and are relied upon for the validation in CSS.

  • required: Specifies that an input field must be filled out.
  • pattern: Defines a regular expression that the input’s value must match.
  • minlength and maxlength: Set the minimum and maximum length for text input.
  • min and max: Set the minimum and maximum values for numerical input.
  • type: Specifies the type of input (e.g., email, number, url).


  <input type="text" required minlength="3" maxlength="20">
  <input type="email" required pattern="[a-z0-9._%+-]+@[a-z0-9.-]+.[a-z]{2,}$">
  <input type="number" min="0" max="100">
  <input type="submit" value="Submit">

CSS Pseudo-classes for Form Validation

On the CSS side of this, it provides several pseudo-classes that allow us to style form elements based on their validation state:

  • :valid: Applies to elements with valid input.
  • :invalid: Applies to elements with invalid input.
  • :required: Matches required elements.
  • :optional: Matches optional elements.
  • :in-range: Applies to elements with a value within specified range.
  • :out-of-range: Applies to elements with a value outside the specified range.

Basic Form Validation Styling

input:valid {
  border: 2px solid green;

input:invalid {
  border: 2px solid red;

input:required {
  border-left: 4px solid blue;

This immediately invalidates before users even interact with these inputs.

premature fom validation css

This is not the desired behavior, as the validation should only happen when you interact with subject. Thankfully, modern CSS is much more powerful, and JavaScript wouldn’t need to intervene.

Preventing Premature Invalid Styling

One way we can solve this is introducing the :not() and :placeholder-shown pseudo-classes:

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

input:valid:not(:placeholder-shown) {
  border: 2px solid green;

Now, the invalid style will only apply if the input is invalid, not focused, and the placeholder is not shown (meaning the user has entered something).

Invalidation will only happen when the blurs the subject.

form validation css

Notice that username remains in it default state since it has not be interacted with yet!

Know, this is definitely a trick some of us might prefer or find tasking, but there is a native way to solve this built right into CSS.

Another method is:

Instead of valid or invalid use the user-valid or user-invalid

input:user-valid {
  border: 2px solid green;

input:user-invalid {
  border: 2px solid red;

This will allow the validation to happen only when the user interacts with it.

Custom Validation Messages

While browsers provide default validation messages, we can create custom ones using the ::after pseudo-element and the :invalid pseudo-class:

input:user-invalid + span::after {
  content: "⚠ Please enter a valid value";
  color: red;
  display: block;
  margin-top: 5px;

input[type=number]:user-invalid + span::after {
  content: "⚠ Please enter a number between 1 - 19";
  color: red;
  display: block;
  margin-top: 5px;

invalid form with custom message

Remember to add a <span> element after each input in your HTML for this to work.

Styling Specific Input Types

Different input types may require different validation styles. Here’s an example for email inputs:

input[type="email"]:user-invalid {
  background: url('error-icon.svg') no-repeat 95% 50%;
  background-size: 25px;
  padding-right: 30px;

This adds an error icon to invalid email inputs.

Range Validation Styling

For numerical inputs with min and max values, the in-range and out-of-range pseudo-class are available:

input[type="number"]:in-range {
  background-color: #e8f0fe;

input[type="number"]:out-of-range {
  background-color: #ffdddd;

Password Strength Indicator

Using these capabilities, we can create a simple password strength indicator using just HTML5 and CSS:

<input type="password" id="password" pattern="(?=.*d)(?=.*[a-z])(?=.*[A-Z]).{8,}" required />
<div class="strength-meter"></div>

Don’t try to understand the regex, just take it as it is 😅 .

#password:user-valid + .strength-meter::before {
  content: "Strong password";
  color: green;

#password:user-invalid + .strength-meter::before {
  content: "Weak password";
  color: red;

#password[pattern]:user-valid + .strength-meter::before {
  content: "Very strong password";
  color: darkgreen;

This provides feedback based on whether the password meets the specified pattern.

Styling Form Submission

Also, while the form is invalid, we can disable the submit button just by targeting the :invalid pseudo-class:

form:invalid button[type="submit"] {
  opacity: 0.5;
  pointer-events: none;

Accessibility Considerations

While visual cues are helpful, we should also consider users who rely on screen readers. We can use aria-invalid and aria-describedby attributes to improve accessibility:

<input type="email" required aria-describedby="email-error">
<span id="email-error" role="alert"></span>
input:invalid:not(:focus):not(:placeholder-shown) {
  aria-invalid: true;

input:user-invalid + #email-error::after {
  content: "Please enter a valid email address";

However, it’s important to note that CSS validation is a visual only form of validation that can significantly reduce the need for JavaScript, it’s not a complete replacement for server-side validation. Always validate form submissions on the server to ensure data integrity.

As browser support for new CSS features continues to improve, we can expect even more awesome styling and improved capabilities in the future.


The :user-valid and :user-invalid pseudo-classes

CSS3 Pseudo-Classes and HTML5 Form - HTML5 Doctor

Stay Super Awesome!

Tagged with: CSS CSS tips CSS Jots form validation
Love it? Share it!