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:
<ul>
<li>fill name/age field</li>
<li>select one of gender fields</li>
<li>select at least one from diet fields</li>
</ul>
<form>
<fieldset>
<input placeholder="Name" type="text" />
<input placeholder="Age" type="text" />
</fieldset>
<fieldset class="oneliner">
<legend>Gender</legend>
<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>
<fieldset class="oneliner">
<legend>Diet</legend>
<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>
</fieldset>
</form>
The CSS code:
@import url(https://fonts.googleapis.com/css?family=PT+Sans+Narrow);
/*the main effect*/
form:where(
:has(
input:placeholder-shown,
input[type="radio"]:indeterminate
),
:not(:has(
input[type="checkbox"]:checked)
)
)::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%);
content:'';
position: fixed;
top:0;
left:0;
width: 100vw;
height:100vh;
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;
}