In this example, we include extra <span> elements to generate content that indicates valid or invalid data:
html
<fieldset>
<legend>Contact information</legend>
<p>Required fields are labeled with "required".</p>
<div>
<label for="fname">First name: </label>
<input id="fname" name="fname" type="text" required />
<span></span>
</div>
<div>
<label for="lname">Last name: </label>
<input id="lname" name="lname" type="text" required />
<span></span>
</div>
<div>
<label for="email"> Email address (include if you want a response): </label>
<input id="email" name="email" type="email" />
<span></span>
</div>
</fieldset>
To provide these indicators, we use the following CSS:
body {
font-family: sans-serif;
margin: 20px auto;
max-width: 460px;
}
fieldset {
padding: 10px 30px 0;
}
legend {
color: white;
background: black;
padding: 5px 10px;
}
fieldset > div {
margin-bottom: 20px;
display: flex;
flex-flow: row wrap;
}
label,
input {
display: block;
font-family: inherit;
margin: 0;
box-sizing: border-box;
width: 100%;
padding: 5px;
height: 30px;
}
input {
box-shadow: inset 1px 1px 3px #cccccc;
border-radius: 5px;
}
input:hover,
input:focus {
background-color: #eeeeee;
}
input:required + span::after {
font-size: 0.7rem;
position: absolute;
content: "required";
color: white;
background-color: black;
padding: 5px 10px;
top: -26px;
left: -70px;
}
css
input + span {
position: relative;
}
input + span::before {
position: absolute;
right: -20px;
top: 5px;
}
input:invalid {
border: 2px solid red;
}
input:invalid + span::before {
content: "✖";
color: red;
}
input:valid + span::before {
content: "✓";
color: green;
}
We set the <span>s to position: relative so that we can position the generated content relative to them. We then absolutely position different generated content depending on whether the form's data is valid or invalid — a green check or a red cross, respectively. To add a bit of extra urgency to the invalid data, we've also given the inputs a thick red border when invalid.
Note:
We've used ::before to add these labels, as we were already using ::after for the "required" labels.
You can try it below:
Notice how the required text inputs are invalid when empty, but valid when they have something filled in. The email input on the other hand is valid when empty, as it is not required, but invalid when it contains something that is not a proper email address.