Traditionally, developers have relied on JavaScript or jQuery to validate their forms, but this approach can be complex, time-consuming, and potentially less accessible. Fortunately, CSS has introduced a new pseudo-class called :has, which allows you to validate your form using CSS alone, without the need for any scripting language.

We have a form that has required input elements that need to be completed, radio fields that need to be checked(one of them) and checkboxes where at least 1 needs to be checked.

In order to achieve this I used the :where, :has and :not pseudo classes. The ::after pseudo element is just for creating a text that is conditionally displayed, depending of the 3 validation rules that I mentioned before.

Let’s write our CSS:

First I used the :where pseudo class because we need 2 selectors that is targeting the form: the :has parent selector that has certain children elements and the :not(:has) parent selector that should not have certain elements inside in order to create that ::after pseudo element.

So we check in the first round, if the form has an input that was not filled in with value(placeholder is still showing), with the :placeholder-shown pseudo class. Then we also check if the radio inputs are still in the indeterminate state(they haven’t been touched[checked/unchecked] yet).

In the next round we look for the lack of checked checkboxes with the :not(:has) pseudo class combination. We could have looked for input[type=”checkbox”]:not(:checked) also but that would look for any unchecked checkbox, but that would mean that all checkboxes need to be checked instead of just 1 or 2 or any number besides 0.

That is all the trick ! For a visual explanation take a look at my Youtube short:

The HTML code:

  <li>fill name/age field</li>
  <li>select one of gender fields</li>
  <li>select at least one from diet fields</li>
      <input placeholder="Name" type="text" />
      <input placeholder="Age" type="text" />
    <fieldset class="oneliner">
      <input type="radio" name="gender" id="male" />
      <label for="male">Male</label>
      <input type="radio" name="gender" id="female" />
      <label for="male">Female</label>
    <fieldset class="oneliner">
      <input type="checkbox" id="fruits" />
      <label for="male">Fruits</label>
      <input type="checkbox" id="vegetables" />
      <label for="male">Vegetables</label>
      <input type="checkbox" id="seeds" />
      <label for="male">Seeds</label>
      <input type="checkbox" id="tea" />
      <label for="male">Tea</label>

The CSS code:

@import url(https://fonts.googleapis.com/css?family=PT+Sans+Narrow);

/*the main effect*/
)::after {
        content: 'Please fill out/check all required fields!';
        color: red;

/*the rest of styling for the look and feel*/
body {
  font-family: 'PT Sans Narrow', Arial, sans-serif;
  color: #333;
  padding: 100px 150px;
body:before {
   opacity: 0.7;
   background-image: linear-gradient(-45deg, #85FFBD 0%, #FFFB7D 100%);
  position: fixed;
  width: 100vw;
  z-index: -1

ul {
  counter-reset: index;  
  padding: 0;
  max-width: 300px;

/* List element */
li {
  counter-increment: index; 
  display: flex;
  align-items: center;
  padding: 2px 0;
  box-sizing: border-box;

/* Element counter */
li::before {
  content: counters(index, ".", decimal-leading-zero);
  font-size: 1.5rem;
  text-align: right;
  font-weight: bold;
  min-width: 50px;
  padding-right: 12px;
  font-variant-numeric: tabular-nums;
  align-self: flex-start;
  background-image: linear-gradient(to bottom, aquamarine, orangered);
  background-attachment: fixed;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;

/* Element separation */
li + li {
  border-top: 1px solid rgba(255,255,255,0.2);

form {
  border: 2px solid #fff;
  border-radius: 10px;
  display: inline-block;
  padding: 30px;

input[type=text] {
  font-family: inherit;
  display: block;
  margin-bottom: 10px;
  padding: 13px 22px;
  border-radius: 5px;
  border: 1px solid #dde3ec;
  background: #ffffff;
  font-weight: 500;
  font-size: 16px;
  color: #536387;
  outline: none;
input:focus {
  border-color: #6a64f1;
  box-shadow: 0px 3px 8px rgba(0, 0, 0, 0.05);
fieldset {
  border: 0;
  padding: 0 0  20px 0;
  margin: 0;
fieldset.oneliner {
  display: flex;
  gap: 5px
legend {
  padding-bottom: 10px;
  font-weight: bold;

How useful was this post?

Click on a star to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.

Categorized in:

Tagged in: