Clear feedback helps people fix mistakes fast. These patterns show valid and invalid states, tooltips, error summaries, requirement lists and success messages.
1Custom validation styles
needs-validation with novalidate shows Bootstrap feedback instead of browser bubbles on submit.
<form class="row g-3 needs-validation" novalidate>
<div class="col-md-6">
<label for="cv-first-329" class="form-label">First name</label>
<input type="text" class="form-control" id="cv-first-329" value="Samir" required>
<div class="valid-feedback">
Looks good!
</div>
<div class="invalid-feedback">
Please enter your first name.
</div>
</div>
<div class="col-md-6">
<label for="cv-email-330" class="form-label">Email</label>
<input type="email" class="form-control" id="cv-email-330" required>
<div class="invalid-feedback">
Please enter a valid email.
</div>
</div>
<div class="col-12">
<label for="cv-city-331" class="form-label">City</label>
<input type="text" class="form-control" id="cv-city-331" required>
<div class="invalid-feedback">
Please enter your city.
</div>
</div>
<div class="col-12">
<button class="btn btn-primary" type="submit">Submit form</button>
</div>
</form>2Server-side states
Add is-valid or is-invalid directly when the server has checked the values.
<div class="row g-3">
<div class="col-md-6">
<label for="sv-user-332" class="form-label">Username</label>
<input type="text" class="form-control is-valid" id="sv-user-332" value="samir86">
<div class="valid-feedback">
Username is available.
</div>
</div>
<div class="col-md-6">
<label for="sv-code-333" class="form-label">Referral code</label>
<input type="text" class="form-control is-invalid" id="sv-code-333" value="REF-747">
<div class="invalid-feedback">
This code has expired.
</div>
</div>
</div>3Tooltip feedback
invalid-tooltip floats the message over the layout in a position-relative parent.
<form class="row g-3 pb-4">
<div class="col-md-6 position-relative">
<label for="tt-name-334" class="form-label">Company</label>
<input type="text" class="form-control is-valid" id="tt-name-334" value="Lyraara Studio">
<div class="valid-tooltip">
Verified
</div>
</div>
<div class="col-md-6 position-relative">
<label for="tt-zip-335" class="form-label">Postal code</label>
<input type="text" class="form-control is-invalid" id="tt-zip-335" value="12">
<div class="invalid-tooltip">
Postal codes have 5 digits.
</div>
</div>
</form>4Supported elements
Textareas, checks, radios, selects and file inputs all support validation states.
<form>
<div class="mb-3">
<label for="se-desc-336" class="form-label">Description</label>
<textarea class="form-control is-invalid" id="se-desc-336" required></textarea>
<div class="invalid-feedback">
Please add a description.
</div>
</div>
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input is-invalid" id="se-agree-337" required>
<label class="form-check-label" for="se-agree-337">Confirm the details are correct</label>
<div class="invalid-feedback">
Please confirm.
</div>
</div>
<div class="form-check">
<input type="radio" class="form-check-input is-invalid" id="se-r1-339" name="se-radio-338" required>
<label class="form-check-label" for="se-r1-339">Pay now</label>
</div>
<div class="form-check mb-3">
<input type="radio" class="form-check-input is-invalid" id="se-r2-340" name="se-radio-338" required>
<label class="form-check-label" for="se-r2-340">Pay later</label>
<div class="invalid-feedback">
Choose a payment option.
</div>
</div>
<div class="mb-3">
<label for="se-select-341" class="form-label">Category</label>
<select class="form-select is-invalid" id="se-select-341" required>
<option selected disabled value="">Choose...</option>
<option>Yoga</option>
</select>
<div class="invalid-feedback">
Select a category.
</div>
</div>
<div>
<label for="se-file-342" class="form-label">Attachment</label>
<input type="file" class="form-control is-invalid" id="se-file-342" required>
<div class="invalid-feedback">
Attach a file.
</div>
</div>
</form>5Browser defaults
Without novalidate the browser shows its own messages for required and typed fields.
<form class="d-flex flex-column flex-sm-row gap-2 align-items-sm-end">
<div class="flex-grow-1">
<label for="bd-email-343" class="form-label">Email for the waitlist</label>
<input type="email" class="form-control" id="bd-email-343" required placeholder="you@example.com">
</div>
<button type="submit" class="btn btn-primary">Join waitlist</button>
</form>6Password requirements
A live-looking checklist below the field shows which rules are met.
- At least 8 characters
- Contains a number
- Contains an uppercase letter
- Contains a symbol
<label for="pr-pass-344" class="form-label">Create password</label>
<input type="password" class="form-control" id="pr-pass-344" value="harbor74" aria-describedby="pr-rules-345">
<ul id="pr-rules-345" class="list-unstyled small mt-2 mb-0">
<li class="text-success"><i class="bi bi-check-circle-fill" aria-hidden="true"></i> At least 8 characters</li>
<li class="text-success"><i class="bi bi-check-circle-fill" aria-hidden="true"></i> Contains a number</li>
<li class="text-danger"><i class="bi bi-x-circle-fill" aria-hidden="true"></i> Contains an uppercase letter</li>
<li class="text-danger"><i class="bi bi-x-circle-fill" aria-hidden="true"></i> Contains a symbol</li>
</ul>7Valid feedback
Positive messages confirm that a value has been accepted.
<div class="mb-3">
<label for="vf-name-346" class="form-label">Team name</label>
<input type="text" class="form-control is-valid" id="vf-name-346" value="Mirao">
<div class="valid-feedback">
Nice - that name is free.
</div>
</div>
<div>
<label for="vf-mail-347" class="form-label">Billing email</label>
<input type="email" class="form-control is-valid" id="vf-mail-347" value="samir.patel@example.com">
<div class="valid-feedback">
We will send receipts here.
</div>
</div>8Input group validation
has-validation on the group keeps corners rounded around the feedback.
<div class="mb-3">
<label for="vi-amount-348" class="form-label">Withdrawal amount</label>
<div class="input-group has-validation">
<span class="input-group-text">$</span> <input type="number" class="form-control is-invalid" id="vi-amount-348" value="8682">
<div class="invalid-feedback">
Maximum per day is $5,000.
</div>
</div>
</div>
<div>
<label for="vi-site-349" class="form-label">Subdomain</label>
<div class="input-group has-validation">
<input type="text" class="form-control is-valid" id="vi-site-349" value="process-launch"> <span class="input-group-text">.site</span>
<div class="valid-feedback">
Available.
</div>
</div>
</div>9Floating label validation
Validation states also work with floating labels.
<div class="form-floating mb-3">
<input type="email" class="form-control is-invalid" id="fv-email-350" placeholder="Email" value="samir@">
<label for="fv-email-350">Email</label>
<div class="invalid-feedback">
Missing domain name.
</div>
</div>
<div class="form-floating">
<input type="tel" class="form-control is-valid" id="fv-phone-351" placeholder="Phone" value="+1 (555) 560-7462">
<label for="fv-phone-351">Phone</label>
<div class="valid-feedback">
Phone number verified.
</div>
</div>10Error summary
An alert at the top lists every problem with links to the fields.
Please fix 2 problems
<div class="alert alert-danger" role="alert" id="es-alert-354">
<h3 class="h6 alert-heading"><i class="bi bi-exclamation-triangle-fill" aria-hidden="true"></i> Please fix 2 problems</h3>
<ul class="mb-0 small">
<li><a href="#es-name-352" class="alert-link">Full name is required</a></li>
<li><a href="#es-email-353" class="alert-link">Email address is not valid</a></li>
</ul>
</div>
<div class="mb-3">
<label for="es-name-352" class="form-label">Full name</label>
<input type="text" class="form-control is-invalid" id="es-name-352" aria-describedby="es-alert-354">
</div>
<div>
<label for="es-email-353" class="form-label">Email</label>
<input type="email" class="form-control is-invalid" id="es-email-353" value="samir.example.com" aria-describedby="es-alert-354">
</div>11Inline error with icon
An icon in the feedback line makes errors stand out for scanning.
<label for="ie-iban-355" class="form-label">Account reference</label>
<input type="text" class="form-control is-invalid" id="ie-iban-355" value="ACC-2658-X" aria-describedby="ie-iban-feedback-356">
<div id="ie-iban-feedback-356" class="invalid-feedback d-flex align-items-center gap-1">
<i class="bi bi-exclamation-circle-fill" aria-hidden="true"></i> Only letters, numbers and dashes, no trailing X.
</div>12Pattern attribute
pattern restricts the format; the help text explains it before anyone gets it wrong.
<form class="needs-validation" novalidate>
<label for="pt-zip-357" class="form-label">ZIP code</label>
<input type="text" class="form-control" id="pt-zip-357" pattern="[0-9]{5}" inputmode="numeric" required aria-describedby="pt-zip-help-358" placeholder="77864">
<div id="pt-zip-help-358" class="form-text">
Five digits, for example 28032.
</div>
<div class="invalid-feedback">
Enter five digits.
</div>
<button type="submit" class="btn btn-outline-primary btn-sm mt-2">Check ZIP</button>
</form>13Length limits
minlength and maxlength with a live counter keep handles within bounds.
<div class="d-flex justify-content-between">
<label for="ml-handle-359" class="form-label">Public handle</label>
<small id="ml-handle-count-360" class="text-body-secondary">0 / 20</small>
</div>
<input type="text" class="form-control" id="ml-handle-359" minlength="4" maxlength="20" required data-demo-counter="#ml-handle-count-360" placeholder="samir_workflow">
<div class="form-text">
4 to 20 characters.
</div>14Confirmation mismatch
When two fields must match, mark the second one invalid and say why.
<div class="mb-3">
<label for="cm-email-361" class="form-label">New email</label>
<input type="email" class="form-control" id="cm-email-361" value="samir.patel@example.com">
</div>
<div>
<label for="cm-confirm-362" class="form-label">Confirm new email</label>
<input type="email" class="form-control is-invalid" id="cm-confirm-362" value="samir.patel1@example.com">
<div class="invalid-feedback">
The email addresses do not match.
</div>
</div>15Required agreements
Two required checkboxes with feedback before an account can be created.
<form class="needs-validation" novalidate>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="rt-terms-363" required>
<label class="form-check-label" for="rt-terms-363">I accept the terms of service</label>
<div class="invalid-feedback">
Required to create an account.
</div>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="rt-privacy-364" required>
<label class="form-check-label" for="rt-privacy-364">I have read the privacy notice</label>
<div class="invalid-feedback">
Please confirm you read it.
</div>
</div>
<button type="submit" class="btn btn-primary">Create account</button>
</form>16Success state
After a successful save, a success alert sits above the validated fields.
<div class="alert alert-success d-flex align-items-center gap-2" role="status">
<i class="bi bi-check-circle-fill" aria-hidden="true"></i>
<div>
Your profile was saved 23 min ago.
</div>
</div>
<div class="row g-3">
<div class="col-sm-6">
<label for="ok-name-365" class="form-label">Name</label>
<input type="text" class="form-control is-valid" id="ok-name-365" value="Samir Patel">
</div>
<div class="col-sm-6">
<label for="ok-mail-366" class="form-label">Email</label>
<input type="email" class="form-control is-valid" id="ok-mail-366" value="samir.patel@example.com">
</div>
</div>17Soft warning
Not every message is an error: a warning hint lets people continue.
<label for="wn-guests-367" class="form-label">Number of guests</label>
<input type="number" class="form-control border-warning" id="wn-guests-367" value="11" aria-describedby="wn-guests-help-368">
<div id="wn-guests-help-368" class="form-text text-warning-emphasis">
<i class="bi bi-exclamation-triangle-fill" aria-hidden="true"></i> Groups larger than 8 may need two tables. You can still book.
</div>18Required select
A placeholder option with an empty value makes required selects validate.
<form class="needs-validation" novalidate>
<label for="rs-country-369" class="form-label">Country of residence</label>
<select class="form-select" id="rs-country-369" required>
<option selected disabled value="">Choose...</option>
<option>Japan</option>
<option>United States</option>
<option>Norway</option>
</select>
<div class="invalid-feedback">
Please select a country.
</div>
<button class="btn btn-primary btn-sm mt-2" type="submit">Continue</button>
</form>19Horizontal form validation
Feedback stays under the field column in a horizontal layout.
<div class="row mb-3">
<label for="hv-email-370" class="col-sm-3 col-form-label">Email</label>
<div class="col-sm-9">
<input type="email" class="form-control is-invalid" id="hv-email-370" value="samir@mail.example">
<div class="invalid-feedback">
Use your work email address.
</div>
</div>
</div>
<div class="row">
<label for="hv-phone-371" class="col-sm-3 col-form-label">Phone</label>
<div class="col-sm-9">
<input type="tel" class="form-control is-valid" id="hv-phone-371" value="+1 (555) 723-1203">
<div class="valid-feedback">
Looks good.
</div>
</div>
</div>20Checking availability
A spinner in the addon signals that a value is being checked.
<label for="ck-slug-372" class="form-label">Workspace URL</label>
<div class="input-group">
<input type="text" class="form-control" id="ck-slug-372" value="aeroly-flow"> <span class="input-group-text"><span class="spinner-border spinner-border-sm text-primary" aria-hidden="true"></span><span class="visually-hidden">Checking</span></span>
</div>
<div class="form-text">
Checking availability...
</div>