Forms are an integral part of many websites. They allow us to take user input and then act on the data provided. Some examples of form submissions include filling out information like your name and address during a purchase. Similarly, you may have filled out registration forms to create an account on a website.
Forms on websites generally require users to enter a variety of data. This can include numbers and strings with specific formatting requirements. Check out our tutorial on validating form input using HTML5 and regex.
In this tutorial, we will learn several pseudo-classes that we can integrate into our modules to make them more user friendly.
Indicates whether the input is required or optional
People generally don’t like filling out long forms. While some of your users may fill out all the form fields, most of them will probably fill in the bare minimum. This means that it is in everyone’s best interest to keep forms short. Even if you plan to use some additional fields, make sure you don’t force users to fill them out.
Keeping some fields optional will give users the option to skip providing that information if they are unwilling while still completing the task at hand.
It is also a good practice to inform users in advance which fields are mandatory and which are optional. One way to do this easily is with the help of :required
And :optional
pseudo-classes.
We implement a form that marks required or optional fields. Our form markup would look like this:
1 |
<form>
|
2 |
<div class="input-wrapper"> |
3 |
<label for="fname">Name</label> |
4 |
<input type="text" name="fname" id="fname"> |
5 |
<span></span>
|
6 |
</div>
|
7 |
<div class="input-wrapper"> |
8 |
<label for="uname">Username</label> |
9 |
<input type="text" name="uname" id="uname" required> |
10 |
<span></span>
|
11 |
</div>
|
12 |
<div class="input-wrapper"> |
13 |
<label for="email">Email Address</label> |
14 |
<input type="email" name="email" id="email" required placeholder="hello@me.com">> |
15 |
<span></span>
|
16 |
</div>
|
17 |
<div class="input-wrapper"> |
18 |
<label for="password">Password</label> |
19 |
<input type="text" name="password" id="password"> |
20 |
<span></span>
|
21 |
</div>
|
22 |
<button>Submit</button> |
23 |
</form>
|
There are two approaches we could take here. The first approach would have involved us marking the required fields as such in the markup itself. The second uses pseudo-elements that we will attach to the file span
tag added after each input
element.
We need extra span
label why ::before
And ::after
do not work input
elements. The span tag must also be after the file input
elements because there are currently no selectors to target elements that come before the current element. However, we will be able to use the adjacent sibling selector to target the span
elements. The following CSS will add a label for optional and required input elements appropriately:
1 |
input { |
2 |
border-radius: 5px; |
3 |
border: 2px solid black; |
4 |
padding: 1rem; |
5 |
}
|
6 |
|
7 |
input + span { |
8 |
position: relative; |
9 |
}
|
10 |
|
11 |
input + span::before { |
12 |
font-size: 60%; |
13 |
font-weight: bold; |
14 |
color: white; |
15 |
padding: 0.5rem; |
16 |
font-weight: bold; |
17 |
width: 80px; |
18 |
position: absolute; |
19 |
left: 0.5rem; |
20 |
text-align: center; |
21 |
top: -1rem; |
22 |
border-radius: 5px; |
23 |
}
|
24 |
|
25 |
input:required + span::before { |
26 |
content: "Required"; |
27 |
background: #3949AB; |
28 |
}
|
29 |
|
30 |
input:not(:required) + span::before { |
31 |
content: "Optional"; |
32 |
background: #2196F3; |
33 |
}
|
The span
element has been positioned relatively so that absolute positioning can be used on ::before
pseudo-element with respect to span
element. We use the :not
selector in combination with :required
to filter the required items. You can see the results in the following CodePen demo:
Indicates whether the input is valid or invalid
In our other tutorial, we learned how to validate form input. Now we’ll see how we can visually manipulate form elements to indicate whether the input is valid or invalid. Two pseudo-classes we use for this purpose are :valid
And :invalid
.
The :valid
selector will match any input or form element whose content has been successfully validated. Let’s say we have an input field that accepts email addresses, we could make the input green in case it is deemed valid. Similarly, we may also limit the length of usernames to between 4 and 12 characters, and so on.
The :invalid
selector will match any input or form element whose content cannot be properly validated. For example, let’s say you want the username to be at least 4 characters long but a user only enters 3 characters. In this case, the input field will match :invalid
selector.
Another thing to keep in mind is that a required input will remain invalid as long as it is empty. On the other hand, optional inputs will remain valid even if you leave them blank.
We’ll expand on the previous example using exactly the same markup and just adding the following CSS to add labels on valid and invalid inputs.
1 |
input + span::before, input + span::after { |
2 |
font-size: 60%; |
3 |
font-weight: bold; |
4 |
color: white; |
5 |
padding: 0.25rem 0.5rem; |
6 |
font-weight: bold; |
7 |
width: 80px; |
8 |
position: absolute; |
9 |
left: 0.5rem; |
10 |
text-align: center; |
11 |
top: -0.8rem; |
12 |
border-radius: 5px; |
13 |
}
|
14 |
|
15 |
input:valid + span::after { |
16 |
content: "Valid"; |
17 |
background: #388E3C; |
18 |
top: 1.2rem; |
19 |
}
|
20 |
|
21 |
input:invalid + span::after { |
22 |
content: "Invalid"; |
23 |
background: #C62828; |
24 |
top: 1.2rem; |
25 |
}
|
Setting the value of top
property a 1.2rem allows us to place the pseudo-element slightly lower than it would naturally be. Most other CSS properties are shared between files ::before
And ::after
pseudo-elements. The following CodePen demo shows the final result:
As you can see, the name and email fields are valid by default because they are optional. However, try to fill the email field with something invalid like word Potato and you will see the label content change from valid to invalid.
At this point, the form looks very crowded. I’ve added labels for each possibility to show you how to implement them yourself. Now we’re going to make some changes to the CSS to remove unnecessary labels.
We can get rid of Optional text while keeping the required fields labeled as Necessary. It would be implied that other fields are optional. Another change we will be making is the removal of :valid
pseudo-class selector to mark fields as valid.
The label marking fields such as Invalid it also seems pointless for now because users haven’t even interacted with the form elements. Another alternative selector we can use instead of :invalid
and the :user-invalid
selector that invalidates an element only after the user has interacted with it.
Browser support is very limited for the :user-invalid
currently. However, it is expected to increase in the future.
Here is the CSS that accomplishes all of this:
1 |
input:required + span::before { |
2 |
content: "Required"; |
3 |
background: black; |
4 |
}
|
5 |
|
6 |
input:user-invalid { |
7 |
border: 2px solid red; |
8 |
}
|
9 |
|
10 |
input:user-invalid + span::after { |
11 |
content: "Invalid"; |
12 |
background: #C62828; |
13 |
top: 1.2rem; |
14 |
}
|
15 |
|
16 |
input:not(:required):user-invalid + span::after { |
17 |
top: 0.2rem; |
18 |
}
|
The last selector applies to optional elements that may become invalid after user interaction. We use this selector to position the label in a slightly higher position.
Let’s also tweak the markup a bit with the minlength
And maxlength
attributes. Here is the final markup:
1 |
<form>
|
2 |
<div class="input-wrapper"> |
3 |
<label for="fname">Name</label> |
4 |
<input type="text" name="fname" id="fname" minlength="3" maxlength="10"> |
5 |
<span></span>
|
6 |
</div>
|
7 |
<div class="input-wrapper"> |
8 |
<label for="uname">Username</label> |
9 |
<input type="text" name="uname" id="uname" minlength="4" maxlength="10" pattern="[a-zA-Z0-9]+" required> |
10 |
<span></span>
|
11 |
</div>
|
12 |
<div class="input-wrapper"> |
13 |
<label for="email">Email Address</label> |
14 |
<input type="email" name="email" id="email" placeholder="hello@me.com"> |
15 |
<span></span>
|
16 |
</div>
|
17 |
<div class="input-wrapper"> |
18 |
<label for="password">Password</label> |
19 |
<input type="password" name="password" id="password" minlength="8" required> |
20 |
<span></span>
|
21 |
</div>
|
22 |
<button>Submit</button> |
23 |
</form>
|
There are many other attributes that you can use for input validation. Placing proper regular expressions inside template attributes makes the validation even more powerful by helping you specify rules for usernames, passwords or email addresses etc. In our example here, we’ll stick to the basics.
Open the demo in Firefox to see the file :user-invalid
class in action.
The name inside the input field must now be at least 3 characters long. Try entering just two characters before moving on to the next field and you will see the Invalid label. Similarly, try entering an invalid email address in the email input field and you will see it becomes invalid as soon as the email field loses focus.
Style of disabled and enabled form elements
Sometimes, we have to develop forms where some elements of the form need to be disabled based on user input. This could be because these values are no longer required to be populated, or because they have been pre-populated with some values based on other data.
You can target disabled elements in your forms using the :disabled
pseudo-class. Disabled items cannot be selected or clicked to provide any input. They also stop focusing. Input elements can be disabled using the disabled
attribute.
Form input elements are enabled by default and you can target them using the :enabled
selector. Enabled form elements can be selected and brought into focus. You can also give them text or other input.
In this section, I will show you a simple example where we will disable two input elements and their values will be dictated by other input elements. We will use the following CSS to indicate that the elements are disabled:
1 |
input:disabled { |
2 |
border: 2px solid gray; |
3 |
color: gray; |
4 |
background: #EEE; |
5 |
}
|
Here is the JavaScript that tracks any changes in the quantity input value and automatically updates the unit price.
1 |
const quantityElem = document.querySelector("input#quantity"); |
2 |
const priceElem = document.querySelector('input#price'); |
3 |
|
4 |
quantityElem.addEventListener('change', (event) => { |
5 |
|
6 |
let quantity = event.target.value; |
7 |
let price = 200; |
8 |
if(quantity >= 10) { |
9 |
let deduction = Math.floor(quantity/5)*5; |
10 |
price -= deduction; |
11 |
}
|
12 |
|
13 |
priceElem.value = price; |
14 |
});
|
The following CodePen shows it in action. You won’t be able to focus on the disabled items or change their values, but they will be updated automatically.
Final thoughts
We should always try to make the form filling process as easy as possible for users. Using these selectors will help us provide visual cues to users about required or optional inputs, valid or invalid inputs, and disabled inputs that are enabled.
You should also try to make your needs for filling out forms very clear to users. For example, if usernames must use a specific set of characters or stay within the character length limit, you should let a user know in advance. Read this tutorial on form validation to find out how we can show useful messages to users in case of errors.