Form Validation Styles
Showing valid and invalid states on form fields using Bootstrap's validation classes and browser APIs.
2 min de lecture
Bootstrap doesn't validate form data for you — it has no idea whether an email address is well-formed or a required field was left blank. What it provides is the visual language for validation states, which you pair with either native HTML validation or your own JavaScript checks.
The two validation classes
Every form control can carry .is-valid or .is-invalid, which color its border and background icon green or red:
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control is-invalid" id="username" value="ab" />
<div class="invalid-feedback">
Username must be at least 3 characters.
</div>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control is-valid" id="password" />
<div class="valid-feedback">
Looks good!
</div>
</div>.invalid-feedback and .valid-feedback are hidden by default and only appear once the sibling input has .is-invalid or .is-valid applied — which means you always add both the state class and the matching feedback message together; the feedback text does nothing on its own.
Pairing with native HTML validation
Rather than manually toggling those classes with JavaScript for every field, Bootstrap has a documented pattern that works with the browser's built-in required, minlength, type="email", and similar constraint attributes:
<form class="needs-validation" novalidate>
<div class="mb-3">
<label for="signupEmail" class="form-label">Email</label>
<input type="email" class="form-control" id="signupEmail" required />
<div class="invalid-feedback">Please enter a valid email address.</div>
</div>
<button class="btn btn-primary" type="submit">Sign up</button>
</form>
<script>
document.querySelectorAll(".needs-validation").forEach((form) => {
form.addEventListener("submit", (event) => {
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
}
form.classList.add("was-validated");
});
});
</script>novalidate on the <form> turns off the browser's default (and inconsistently styled) validation bubbles, so Bootstrap's own styling can take over instead. form.checkValidity() still runs the browser's real validation logic behind the scenes — checking required, type="email", minlength, and any pattern attribute — it's just that adding the .was-validated class is what tells Bootstrap's CSS to start showing .is-valid/.is-invalid styling based on each field's :valid/:invalid pseudo-class state.
This matters because it means the actual validation logic (is this really a valid email?) still comes from the browser's native constraint validation API, not from Bootstrap — Bootstrap is only responsible for how that pass/fail state looks once the browser has already determined it.
When you need custom validation logic
If your rules go beyond what HTML's built-in attributes can express — checking a username against a server, confirming two passwords match — you apply the same .is-valid/.is-invalid classes yourself, from your own JavaScript, instead of relying on .was-validated:
const password = document.getElementById("password");
const confirm = document.getElementById("confirmPassword");
confirm.addEventListener("input", () => {
const matches = confirm.value === password.value;
confirm.classList.toggle("is-valid", matches);
confirm.classList.toggle("is-invalid", !matches);
});Either way, the visual vocabulary — colored borders, .invalid-feedback messages — stays the same, whether the "was this valid?" decision came from the browser or from your own code.