Bootstrap Integration

Integrate SpamBlock with Bootstrap forms and handle status messages with Bootstrap styling

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 submit event in the capture phase to prevent native submission
  • Use FormData to 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

  1. Always show loading state: Disable the submit button and show a spinner while SpamBlock is checking
  2. Clear messages: Hide previous alerts before showing new ones
  3. Reset form on success: Clear the form after successful submission
  4. Auto-hide messages: Use setTimeout to hide success messages after a few seconds
  5. 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