CSS `n of` Selectors for Conditional Validation
CSS `n of` Selectors for Conditional Validation êŽë š
Sometimes we do a cursory check on user input controls before theyâre all set for data validation and submission. For instance, a form might only unlock its submit button when the user has filled in a certain number of fields. Or a user needs to fill in a field in the right way to take the next step.
This is a great way to cut down on submission mistakes and help users make sure everything is as right as possible for submission with less hassle. Itâs even more effective when designed to give real-time feedback.
CSS lets you handle some of these simple checks right away, even before the browserâs validation routine runs following a submission trigger. The :valid and :invalid pseudo-classes are good examples of that. They allow browsers style fields as you type, so you can see if the data youâre entering is in the right format.
Similarly, the n of <selector-list> syntax in the :nth-child selector can also be very useful for a preliminary validation. First, letâs see what it does.
About n of selectors
The :nth-child() selector picks elements from all siblings, counting from the first. The n of selectors syntax version does the same, but only from siblings matching the given selectors.
/* selects second element */
:nth-child(2) { }
/* selects second of all elements with class marked */
:nth-child(2 of .marked) { }
Although not explicitly, in a rudimentary way, n of selectors can keep count of a selectorâs matching elements. The same also works for pseudo-classes which usually indicate the state of an element.
label:nth-child(2 of :has(:checked)) {
color: red;
}
Say, in a todo list, we want to give the user a sense of accomplishment as they clear items, perhaps every three? n of selectors can help with that, because we will know when a third or sixth :checked box appears in the list.
.list:has(:nth-child(3 of :checked)) {
/* A cheer for user on getting three things done in the list */
}
The demo below the second of all labels with a :checked box as a decendent.
Possible Webkit bug (April 2026)
In some contexts, state-based styling with ân of selectorâ may not work as expected in Webkit browsers. Compare with Firefox results.
We apply the same idea to our initial check of input controls. Imagine a form where the user needs to fill out at least three fields. We give them a heads-up about this requirement before they begin, and once theyâve completed three fields, we let them know theyâve met that requirement. Iâll use simple validations in the demo to check if the fields are filled, but you can use any validation you prefer.
The Example
Here are the fields:
<div class="field">
<!-- email label -->
<input type="email" placeholder="jane.doe@example.org">
</div>
<div class="field">
<!-- social url label -->
<input type="url" placeholder="https://linkedin.com/janedoe">
</div>
<div class="field">
<!-- social handle label -->
<input type="text" placeholder="@username">
</div>
<div class="field">
<!-- gender label -->
<span>
<input type="radio" name="gender"> <!-- male label -->
<input type="radio" name="gender"> <!-- female label -->
<input type="radio" name="gender"> <!-- other label -->
</span>
</div>
<div class="field">
<!-- contact preference label -->
<select id="contact-preference">
<option value="" selected><!-- text --></option>
<option value="email"><!-- text --></option>
<option value="phone"><!-- text --></option>
<option value="whatsapp"><!-- text --></option>
<option value="sms"><!-- text --></option>
</select>
</div>
Selectors for Validating
For the text, URL, and email, Iâll check if the placeholder is visible. If it isnât, Iâll know the user has filled it in. For the radio group, Iâll check if itâs :checked. And for the select box, Iâll look for the absence of the :checked state in the option with empty string value.
[type="email"]:not(:placeholder-shown),
[type="url"]:not(:placeholder-shown),
[type="text"]:not(:placeholder-shown),
[type="radio"]:checked,
option[value=""]:not(:checked) {}
If any of the selectors above match, Iâll assume the user has filled a field. If the user has filled three or more fields, 3 of selectors-for-filled-fields syntax will match the third of all the filled fields. First, let me combine the selectors that use placeholder to shorten the code.
:where([type="email"],
[type="url"],
[type="text"]):not(:placeholder-shown) {}
Validation
To select .fields that have these controls:
.field:has(
:where([type="email"],[type="url"],[type="text"]):not(:placeholder-shown),
[type="radio"]:checked,
option[value=""]:not(:checked)
) {}
And finally, to select the 3rd one of these .fields:
:nth-child(3 of .field:has(
:where([type="email"],[type="url"],[type="text"]):not(:placeholder-shown),
[type="radio"]:checked,
option[value=""]:not(:checked))) {
/* Let user know they've filled three fields */
}
More Examples
Hereâs a simplified version where the form becomes submit-able when two of the four (text-only) fields are filled out.
And hereâs another example where a special message is shown after the form when three of the fields are filled out.
Like I mentioned before, you can use any validations you like. For example, you could use select:valid for a required select box thatâs been selected, or :in-range for a user input thatâs within a minâmax range. No matter what types of fields youâre using or how youâre validating them, you can use their validating state selectors to see if a certain number of them have been found.
I suggest letting users know whatâs expected of them when filling out the form and letting them know when the preliminary checks have been cleared.