splitforms.com
All articles/ TUTORIALS10 MIN READPublished June 30, 2026

AJAX Contact Form: Submit Without Page Reload (2026)

Build an AJAX contact form that submits without a page reload in 2026. Vanilla JS, jQuery, and fetch examples with validation and spam protection.

✶ Written by
splitforms.com / blog

Founder of splitforms — the form backend API for developers. Writes about form UX, anti-spam, and shipping web apps without backend code.

What is an AJAX contact form?

A traditional HTML form submits with a full page reload: the browser navigates to the form's action URL, the server processes the request, and a new page loads. An AJAX form intercepts the submit event with JavaScript, sends the data in the background via fetch or XMLHttpRequest, and updates the page without reloading.

The user experience is noticeably better:

  • No page flash or reload
  • Inline success and error messages
  • Loading state on the submit button
  • Form stays visible — user can correct and resubmit
  • Works seamlessly in single-page apps

Vanilla JS AJAX form (recommended)

No libraries. No frameworks. Just the browser's built-in fetch API and a hosted form backend.

<form id="contact-form">
  <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
  <input type="hidden" name="subject" value="New contact from yoursite.com" />

  <div class="form-group">
    <label for="name">Name</label>
    <input id="name" name="name" type="text" required />
  </div>

  <div class="form-group">
    <label for="email">Email</label>
    <input id="email" name="email" type="email" required />
  </div>

  <div class="form-group">
    <label for="message">Message</label>
    <textarea id="message" name="message" rows="5" required></textarea>
  </div>

  <!-- Honeypot spam trap -->
  <input name="botcheck" type="checkbox" style="display:none" tabindex="-1" />

  <button type="submit" id="submit-btn">Send message</button>
  <div id="form-status" role="status" aria-live="polite"></div>
</form>

<script>
const form = document.getElementById('contact-form');
const button = document.getElementById('submit-btn');
const status = document.getElementById('form-status');

form.addEventListener('submit', async (e) => {
  e.preventDefault();

  // Loading state
  button.disabled = true;
  button.textContent = 'Sending...';
  status.textContent = '';

  try {
    const formData = new FormData(form);

    const response = await fetch('https://splitforms.com/api/submit', {
      method: 'POST',
      body: formData,
    });

    if (response.ok) {
      status.innerHTML = '✅ Thanks! We\'ll get back to you within 24 hours.';
      status.style.color = 'green';
      form.reset();
    } else {
      throw new Error('Server error');
    }
  } catch (error) {
    status.innerHTML = '❌ Something went wrong. Please email us directly at hello@splitforms.com.';
    status.style.color = '#dc2626';
  } finally {
    button.disabled = false;
    button.textContent = 'Send message';
  }
});
</script>

This is the recommended approach for most websites. It works in all modern browsers, requires no dependencies, and pairs with the splitforms hosted backend for email delivery and spam filtering.

jQuery AJAX form (legacy)

If your project already uses jQuery, here's the equivalent using $.ajax:

<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script>
$('#contact-form').on('submit', function(e) {
  e.preventDefault();

  var $btn = $('#submit-btn');
  $btn.prop('disabled', true).text('Sending...');

  $.ajax({
    url: 'https://splitforms.com/api/submit',
    method: 'POST',
    data: $(this).serialize(),
    dataType: 'json',
    success: function() {
      $('#form-status').html('✅ Thanks! We\'ll be in touch.').css('color', 'green');
      $('#contact-form')[0].reset();
    },
    error: function() {
      $('#form-status').html('❌ Something went wrong. Try again.').css('color', '#dc2626');
    },
    complete: function() {
      $btn.prop('disabled', false).text('Send message');
    }
  });
});
</script>

Note: $(this).serialize() sends URL-encoded data, which works fine with splitforms. For file uploads, use FormData instead.

React / Next.js AJAX form

In React, the pattern is the same — just with hooks:

'use client';

import { useState, useRef } from 'react';

export default function AjaxContactForm() {
  const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
  const formRef = useRef<HTMLFormElement>(null);

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    setStatus('loading');

    try {
      const formData = new FormData(e.currentTarget as HTMLFormElement);
      const res = await fetch('https://splitforms.com/api/submit', {
        method: 'POST',
        body: formData,
      });

      if (!res.ok) throw new Error('Failed');
      setStatus('success');
      formRef.current?.reset();
    } catch {
      setStatus('error');
    }
  }

  return (
    <form onSubmit={handleSubmit} ref={formRef}>
      <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
      <input name="name" required placeholder="Name" />
      <input name="email" type="email" required placeholder="Email" />
      <textarea name="message" required placeholder="Message" />
      <button type="submit" disabled={status === 'loading'}>
        {status === 'loading' ? 'Sending...' : 'Send'}
      </button>
      {status === 'success' && <p>✅ Thanks! We&apos;ll be in touch.</p>}
      {status === 'error' && <p>❌ Something went wrong.</p>}
    </form>
  );
}

Features to include in every AJAX form

  1. Loading state. Disable the submit button and change its text during the request. Prevents double-submits.
  2. Accessibility. Use aria-live="polite" on the status div so screen readers announce updates.
  3. Honeypot field. A hidden checkbox named botcheckthat bots fill but humans don't. splitforms auto-detects and drops these.
  4. Client-side validation. Use HTML5 required, type="email", and pattern attributes for instant feedback.
  5. Fallback. If JavaScript fails, the form should still submit via the native action attribute.
  6. Timeout. Abort the request after 10 seconds:
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000);

try {
  const res = await fetch(url, {
    method: 'POST',
    body: formData,
    signal: controller.signal,
  });
  clearTimeout(timeout);
} catch (e) {
  if (e.name === 'AbortError') {
    status.textContent = 'Request timed out. Please try again.';
  }
}

Spam protection for AJAX forms

AJAX forms are slightly more vulnerable to spam than native forms because bots can POST directly to the endpoint without loading your HTML page. Three layers of protection:

  • Honeypot — the hidden botcheck field catches naive bots.
  • AI spam classifier — splitforms runs every submission through an AI model that scores spam probability. It catches 99.2% of spam without CAPTCHA.
  • Origin checking — splitforms validates the Origin header against your registered domains. Bots posting from other origins are rejected.

The result: no CAPTCHA, no Google scripts, no GDPR concerns. Just clean spam protection that doesn't annoy real users.

Frequently asked questions

What is an AJAX contact form?

An AJAX contact form submits form data to a server (or API) without reloading the web page. Instead of the browser navigating to a new URL, JavaScript sends the data in the background (via fetch or XMLHttpRequest) and updates the page with a success or error message. This creates a smoother user experience.

How do I create an AJAX contact form?

The simplest approach: add an event listener to the form's submit event, call preventDefault() to stop the page reload, then use fetch() to POST the FormData to your endpoint. For the backend, use a hosted service like splitforms (https://splitforms.com/api/submit) so you don't need to write server code.

Should I use jQuery AJAX or fetch for contact forms?

Use the native fetch API. jQuery's $.ajax is still functional but adds an unnecessary 30KB+ dependency. fetch is supported in all modern browsers, has a cleaner API, and works with async/await. Use jQuery AJAX only if your project already depends on jQuery.

How do I handle AJAX form errors?

Wrap your fetch call in try/catch, check response.ok for HTTP errors, show a user-friendly message on failure, always re-enable the submit button (use a finally block), and implement a retry mechanism. For network errors, consider storing the submission in localStorage and retrying when connectivity returns.

Can I use AJAX with a hosted form backend?

Yes. splitforms supports both native form POST (with redirect) and AJAX submissions (via fetch). For AJAX, POST your FormData to https://splitforms.com/api/submit and handle the JSON response. splitforms sets permissive CORS headers so there are no cross-origin issues.

Ship your AJAX form today

AJAX form + hosted backend = shipping in 5 minutes.
splitforms gives you the API endpoint, spam filtering, email delivery, and dashboard. You write the frontend. Free for 500 submissions/month — no credit card.
Create your free account →

Keep reading

About the author
✻ ✻ ✻

Get your free contact form API key in 60 seconds.

500 free form submissions per month. No credit card. No SDK, no PHP, no plugin. Drop one POST endpoint in your form and submissions land in your dashboard and can notify your inbox on every plan.

Generate access key →Read the docs
founders pricing locked in · early access open