Overview
This guide shows how to integrate SpamBlock with Bootstrap forms, including proper styling for success/error messages and submit button states. SpamBlock works seamlessly with Bootstrap's form components and validation classes.
Prerequisites
- Bootstrap CSS and JavaScript loaded on your page
- Basic knowledge of Bootstrap form styling
- SpamBlock pixel script included
Setup
1. Include SpamBlock Pixel
Add the SpamBlock script to your page:
<script src="https://api.spamblock.io/sdk/pixel/v1.js defer"></script>
2. Basic Bootstrap Form
Here's a contact form with Bootstrap styling:
<form data-block-spam action="/api/contact" method="post" id="contact-form" class="needs-validation" novalidate>
<div class="mb-3">
<label for="email" class="form-label">Email Address</label>
<input
type="email"
class="form-control"
id="email"
name="email"
required
placeholder="[email protected]">
<div class="invalid-feedback">Please provide a valid email address.</div>
</div>
<div class="mb-3">
<label for="message" class="form-label">Message</label>
<textarea
class="form-control"
id="message"
name="message"
rows="4"
required
placeholder="Your message here..."></textarea>
<div class="invalid-feedback">Please provide a message.</div>
</div>
<button type="submit" class="btn btn-primary" id="submit-btn">
<span class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
<span class="btn-text">Send Message</span>
</button>
<!-- Status messages (hidden by default) -->
<div class="alert alert-success mt-3 d-none" id="success-message" role="alert"></div>
<div class="alert alert-danger mt-3 d-none" id="error-message" role="alert"></div>
</form>
3. Event Handling with Bootstrap Styling
Add this JavaScript to handle SpamBlock events and show Bootstrap-styled feedback:
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('contact-form');
const submitBtn = document.getElementById('submit-btn');
const btnText = submitBtn.querySelector('.btn-text');
const spinner = submitBtn.querySelector('.spinner-border');
const successAlert = document.getElementById('success-message');
const errorAlert = document.getElementById('error-message');
// Hide all alerts initially
const hideAlerts = () => {
successAlert.classList.add('d-none');
errorAlert.classList.add('d-none');
};
// Show loading state
const setLoading = (loading) => {
if (loading) {
submitBtn.disabled = true;
spinner.classList.remove('d-none');
btnText.textContent = 'Sending...';
} else {
submitBtn.disabled = false;
spinner.classList.add('d-none');
btnText.textContent = 'Send Message';
}
};
// Handle successful submission
form.addEventListener('spamblock:allowed', (event) => {
setLoading(false);
hideAlerts();
const response = event.detail.response;
// Show success message if provided
if (response && response.allow === true && response.score < 60) {
successAlert.textContent = '✅ Thank you! Your message has been sent successfully.';
successAlert.classList.remove('d-none');
// Reset form after successful submission
form.reset();
// Hide success message after 5 seconds
setTimeout(() => {
successAlert.classList.add('d-none');
}, 5000);
}
});
// Handle blocked submission
form.addEventListener('spamblock:blocked', (event) => {
setLoading(false);
hideAlerts();
const response = event.detail.response;
const reasons = response?.reasons || [];
// Build error message from reasons
let errorMsg = '❌ Your submission was blocked. ';
if (reasons.includes('disposable_domain')) {
errorMsg += 'Please use a valid email address.';
} else if (reasons.includes('profanity_detected')) {
errorMsg += 'Please remove inappropriate language.';
} else if (reasons.includes('honeypot_filled')) {
errorMsg += 'Automated submission detected.';
} else {
errorMsg += `Spam detected (score: ${response?.score || 0}).`;
}
errorAlert.textContent = errorMsg;
errorAlert.classList.remove('d-none');
});
// Show loading state when form is submitted
form.addEventListener('submit', () => {
hideAlerts();
setLoading(true);
});
// Bootstrap validation (optional)
form.addEventListener('submit', (event) => {
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
setLoading(false);
}
form.classList.add('was-validated');
});
});
Advanced Examples
With Bootstrap Icons
<form data-block-spam action="/api/contact" method="post" id="contact-form">
<!-- Form fields -->
<button type="submit" class="btn btn-primary" id="submit-btn">
<i class="bi bi-send me-2"></i>
<span class="btn-text">Send Message</span>
</button>
<!-- Success with icon -->
<div class="alert alert-success mt-3 d-none" id="success-message" role="alert">
<i class="bi bi-check-circle me-2"></i>
<span id="success-text"></span>
</div>
<!-- Error with icon -->
<div class="alert alert-danger mt-3 d-none" id="error-message" role="alert">
<i class="bi bi-exclamation-triangle me-2"></i>
<span id="error-text"></span>
</div>
</form>
Toast Notifications (Bootstrap 5)
// Show success toast
form.addEventListener('spamblock:allowed', (event) => {
setLoading(false);
const toastEl = document.getElementById('success-toast');
const toast = new bootstrap.Toast(toastEl);
toast.show();
// Reset form
form.reset();
});
// Show error toast
form.addEventListener('spamblock:blocked', (event) => {
setLoading(false);
const toastEl = document.getElementById('error-toast');
const toastText = toastEl.querySelector('.toast-body');
const response = event.detail.response;
toastText.textContent = `Submission blocked: ${response?.reasons?.join(', ') || 'Spam detected'}`;
const toast = new bootstrap.Toast(toastEl);
toast.show();
});
Add toast HTML to your page:
<!-- Toast Container -->
<div class="toast-container position-fixed top-0 end-0 p-3">
<div id="success-toast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header bg-success text-white">
<strong class="me-auto">Success</strong>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast"></button>
</div>
<div class="toast-body">Your message has been sent successfully!</div>
</div>
<div id="error-toast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header bg-danger text-white">
<strong class="me-auto">Error</strong>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast"></button>
</div>
<div class="toast-body"></div>
</div>
</div>
JavaScript-Based Submission
If you need to handle submission via JavaScript (for SPAs, custom APIs, or to prevent page navigation), intercept the spamblock:allowed event:
<form data-block-spam id="contact-form" class="needs-validation" novalidate>
<!-- Form fields -->
<button type="submit" class="btn btn-primary" id="submit-btn">
<span class="spinner-border spinner-border-sm d-none" role="status"></span>
<span class="btn-text">Send Message</span>
</button>
<div class="alert alert-success mt-3 d-none" id="success-message"></div>
<div class="alert alert-danger mt-3 d-none" id="error-message"></div>
</form>
<script>
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('contact-form');
const submitBtn = document.getElementById('submit-btn');
const btnText = submitBtn.querySelector('.btn-text');
const spinner = submitBtn.querySelector('.spinner-border');
const successAlert = document.getElementById('success-message');
const errorAlert = document.getElementById('error-message');
const setLoading = (loading) => {
if (loading) {
submitBtn.disabled = true;
spinner.classList.remove('d-none');
btnText.textContent = 'Sending...';
} else {
submitBtn.disabled = false;
spinner.classList.add('d-none');
btnText.textContent = 'Send Message';
}
};
// Prevent native submission by intercepting submit event in capture phase
form.addEventListener('submit', (event) => {
// This runs before SpamBlock's handler, preventing native submission
event.preventDefault();
}, { capture: true });
// Intercept allowed event to handle submission manually
form.addEventListener('spamblock:allowed', async (event) => {
setLoading(true);
errorAlert.classList.add('d-none');
try {
// Submit form data via fetch
const formData = new FormData(form);
const response = await fetch('/api/contact', {
method: 'POST',
body: formData
});
if (response.ok) {
successAlert.textContent = '✅ Thank you! Your message has been sent successfully.';
successAlert.classList.remove('d-none');
form.reset();
setTimeout(() => successAlert.classList.add('d-none'), 5000);
} else {
throw new Error('Submission failed');
}
} catch (error) {
errorAlert.textContent = '❌ Error sending message. Please try again.';
errorAlert.classList.remove('d-none');
} finally {
setLoading(false);
}
});
// Handle blocked submissions
form.addEventListener('spamblock:blocked', (event) => {
setLoading(false);
const response = event.detail.response;
const reasons = response?.reasons || [];
let errorMsg = '❌ Your submission was blocked. ';
if (reasons.includes('disposable_domain')) {
errorMsg += 'Please use a valid email address.';
} else if (reasons.includes('profanity_detected')) {
errorMsg += 'Please remove inappropriate language.';
} else {
errorMsg += `Spam detected (score: ${response?.score || 0}).`;
}
errorAlert.textContent = errorMsg;
errorAlert.classList.remove('d-none');
});
// Show loading state when form is submitted
form.addEventListener('submit', () => {
setLoading(true);
});
});
</script>
Key points:
- Intercept the
submitevent in the capture phase to prevent native submission - Use
FormDatato collect form data - Handle success/error states with Bootstrap alerts
- Form stays on the same page (no navigation)
Form Validation States
Use Bootstrap's validation classes to show field-level feedback:
form.addEventListener('spamblock:blocked', (event) => {
const response = event.detail.response;
const reasons = response?.reasons || [];
// Highlight email field if disposable domain detected
if (reasons.includes('disposable_domain')) {
const emailInput = form.querySelector('[name="email"]');
emailInput.classList.add('is-invalid');
emailInput.classList.remove('is-valid');
const feedback = emailInput.nextElementSibling;
if (feedback && feedback.classList.contains('invalid-feedback')) {
feedback.textContent = 'Please use a valid, non-disposable email address.';
}
}
// Highlight message field if profanity detected
if (reasons.includes('profanity_detected')) {
const messageInput = form.querySelector('[name="message"]');
messageInput.classList.add('is-invalid');
messageInput.classList.remove('is-valid');
const feedback = messageInput.nextElementSibling;
if (feedback && feedback.classList.contains('invalid-feedback')) {
feedback.textContent = 'Please remove inappropriate language from your message.';
}
}
});
Complete Example
Here's a complete, production-ready Bootstrap form with SpamBlock:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contact Form - SpamBlock + Bootstrap</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container my-5">
<div class="row justify-content-center">
<div class="col-md-8">
<h1 class="mb-4">Contact Us</h1>
<form data-block-spam action="/api/contact" method="post" id="contact-form" class="needs-validation" novalidate>
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control" id="name" name="name" required>
<div class="invalid-feedback">Please provide your name.</div>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email Address</label>
<input type="email" class="form-control" id="email" name="email" required>
<div class="invalid-feedback">Please provide a valid email address.</div>
</div>
<div class="mb-3">
<label for="message" class="form-label">Message</label>
<textarea class="form-control" id="message" name="message" rows="5" required></textarea>
<div class="invalid-feedback">Please provide a message.</div>
</div>
<button type="submit" class="btn btn-primary" id="submit-btn">
<span class="spinner-border spinner-border-sm d-none me-2" role="status" aria-hidden="true"></span>
<span class="btn-text">Send Message</span>
</button>
<div class="alert alert-success mt-3 d-none" id="success-message" role="alert"></div>
<div class="alert alert-danger mt-3 d-none" id="error-message" role="alert"></div>
</form>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://api.spamblock.io/sdk/pixel/v1.js" defer></script>
<script>
// Event handling code from above
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('contact-form');
const submitBtn = document.getElementById('submit-btn');
const btnText = submitBtn.querySelector('.btn-text');
const spinner = submitBtn.querySelector('.spinner-border');
const successAlert = document.getElementById('success-message');
const errorAlert = document.getElementById('error-message');
const hideAlerts = () => {
successAlert.classList.add('d-none');
errorAlert.classList.add('d-none');
};
const setLoading = (loading) => {
if (loading) {
submitBtn.disabled = true;
spinner.classList.remove('d-none');
btnText.textContent = 'Sending...';
} else {
submitBtn.disabled = false;
spinner.classList.add('d-none');
btnText.textContent = 'Send Message';
}
};
form.addEventListener('spamblock:allowed', (event) => {
setLoading(false);
hideAlerts();
const response = event.detail.response;
if (response && response.allow === true) {
successAlert.textContent = '✅ Thank you! Your message has been sent successfully.';
successAlert.classList.remove('d-none');
form.reset();
setTimeout(() => successAlert.classList.add('d-none'), 5000);
}
});
form.addEventListener('spamblock:blocked', (event) => {
setLoading(false);
hideAlerts();
const response = event.detail.response;
const reasons = response?.reasons || [];
let errorMsg = '❌ Your submission was blocked. ';
if (reasons.includes('disposable_domain')) {
errorMsg += 'Please use a valid email address.';
} else if (reasons.includes('profanity_detected')) {
errorMsg += 'Please remove inappropriate language.';
} else {
errorMsg += `Spam detected (score: ${response?.score || 0}).`;
}
errorAlert.textContent = errorMsg;
errorAlert.classList.remove('d-none');
});
form.addEventListener('submit', () => {
hideAlerts();
setLoading(true);
});
form.addEventListener('submit', (event) => {
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
setLoading(false);
}
form.classList.add('was-validated');
});
});
</script>
</body>
</html>
Best Practices
- Always show loading state: Disable the submit button and show a spinner while SpamBlock is checking
- Clear messages: Hide previous alerts before showing new ones
- Reset form on success: Clear the form after successful submission
- Auto-hide messages: Use
setTimeoutto hide success messages after a few seconds - Accessible alerts: Use proper ARIA roles and ensure screen readers can announce status changes
Troubleshooting
| Issue | Solution |
|---|---|
| Form submits but no message appears | Check that event listeners are attached after DOM loads |
| Loading state never resets | Ensure setLoading(false) is called in both allowed and blocked handlers |
| Bootstrap validation conflicts | Remove novalidate or handle validation manually in submit handler |
| Messages appear but don't hide | Check that d-none class is being toggled correctly |