Bootstrap Contact Form with Character Counter — Free Snippet
Bootstrap Contact Form with Character Counter · Forms · Plain HTML, CSS & JS · Live preview
Copy & paste
Source Code
<div class="container py-5 d-flex justify-content-center">
<div class="card bscontact-card">
<div class="card-body p-4">
<h5 class="fw-bold mb-3">Send us a message</h5>
<form id="bscontactForm" novalidate>
<div class="mb-3">
<label class="form-label small fw-semibold">Email</label>
<input type="email" class="form-control" id="bscontactEmail" required>
</div>
<div class="mb-1">
<label class="form-label small fw-semibold d-flex justify-content-between">
<span>Message</span>
<span class="text-muted" id="bscontactCount">0 / 280</span>
</label>
<textarea class="form-control" id="bscontactMessage" rows="4" maxlength="280" required></textarea>
</div>
<p class="small text-danger mb-3 d-none" id="bscontactWarn">Keep it under 280 characters.</p>
<button type="submit" class="btn btn-dark w-100 fw-bold">Send message</button>
<p class="small text-success mt-2 mb-0 d-none" id="bscontactSuccess">Message sent — we'll reply within a day.</p>
</form>
</div>
</div>
</div>.bscontact-card { width: 400px; border: 1px solid #eceef1; border-radius: 14px; }const form = document.getElementById('bscontactForm');
const email = document.getElementById('bscontactEmail');
const message = document.getElementById('bscontactMessage');
const countEl = document.getElementById('bscontactCount');
const warn = document.getElementById('bscontactWarn');
const success = document.getElementById('bscontactSuccess');
const LIMIT = 280;
message.addEventListener('input', () => {
const len = message.value.length;
countEl.textContent = len + ' / ' + LIMIT;
const near = len >= LIMIT - 20;
countEl.classList.toggle('text-danger', near);
countEl.classList.toggle('text-muted', !near);
warn.classList.toggle('d-none', len < LIMIT);
});
form.addEventListener('submit', e => {
e.preventDefault();
success.classList.add('d-none');
email.classList.toggle('is-invalid', !email.checkValidity());
message.classList.toggle('is-invalid', !message.value.trim());
if (!email.checkValidity() || !message.value.trim()) return;
success.classList.remove('d-none');
form.reset();
countEl.textContent = '0 / ' + LIMIT;
countEl.classList.remove('text-danger');
countEl.classList.add('text-muted');
[email, message].forEach(f => f.classList.remove('is-invalid'));
});Extend with AI
Take This Snippet Further With AI
The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.
What's included
Features
About this UI Snippet
Bootstrap Contact Form with Character Counter — HTML, CSS & JavaScript

A maxlength attribute on a <textarea> silently stops a visitor from typing further with no visible warning of why — this snippet pairs Bootstrap's real maxlength="280" textarea with a live counter that updates on every keystroke, turning from muted gray to red once fewer than 20 characters remain, so the limit is visible well before it's actually hit rather than discovered by a key that stops responding.
Submission is genuinely validated: both the email (checkValidity()) and the message (a non-empty check) must pass before the success message appears, with Bootstrap's real is-invalid state applied to whichever field fails — built on real Bootstrap 5.3 form controls throughout.
Step by step
How to Use
- 1Load the snippetClick the snippet in the sidebar Library tab. The preview loads an empty contact form with "0 / 280".
- 2Type a messageThe counter updates on every keystroke, e.g. "142 / 280".
- 3Type past 260 charactersThe counter turns red, giving an early warning before the 280 limit is actually reached.
- 4Submit with an empty or invalid emailThe relevant field gets Bootstrap's invalid styling and the form doesn't submit.
- 5Fill both fields correctly and submitA green success message appears and the form resets.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Live — the input event fires on every keystroke, so the counter updates immediately as you type, not after a debounce delay or on blur.
The textarea's native maxlength="280" attribute physically prevents typing further, and the counter has already turned red and shown a warning message before that point, so the limit isn't a surprise.
Yes — the email field is checked with native checkValidity() and the message with a non-empty check; either failing applies Bootstrap's is-invalid class and blocks the success message from showing.
Nowhere outside the page — this is a front-end demo. Replace the success-message logic in the submit handler with a real fetch() call to your contact-form API endpoint.
Yes — update both the LIMIT constant in the JS and the maxlength="280" attribute on the textarea to the same new value, so the visible counter and the actual enforced limit stay in sync.