HTMX Form Submission

Form submission is where most web applications do their heaviest lifting — creating accounts, placing orders, sending messages, and saving records. HTMX transforms the traditional form submit (which reloads the entire page) into a smooth, targeted update that keeps the user exactly where they are. This topic covers the full spectrum of HTMX form handling, from the simplest contact form to multi-step flows.

Traditional Form vs HTMX Form

  TRADITIONAL SUBMIT:
  User fills form → clicks Submit
       |
       v
  Browser sends POST, navigates away
       v
  Entire page reloads
       v
  User sees "success" page — loses scroll position

  HTMX SUBMIT:
  User fills form → clicks Submit
       |
       v
  HTMX sends POST in background
       v
  Server returns small HTML fragment
       v
  Specific div updates with success message
       v
  User stays on the same page — no reload

Basic Form Example

<form
  hx-post="/contact"
  hx-target="#form-feedback"
  hx-swap="innerHTML">

  <label>Name</label>
  <input type="text" name="name" required>

  <label>Email</label>
  <input type="email" name="email" required>

  <label>Message</label>
  <textarea name="message" rows="4" required></textarea>

  <button type="submit">
    Send Message
    <span class="htmx-indicator">Sending...</span>
  </button>
</form>

<div id="form-feedback"></div>

HTMX intercepts the form's submit event. It collects all input values and sends them as a POST request. The server saves the contact and returns a confirmation fragment that appears in #form-feedback.

What Data Gets Sent

HTMX collects every input, textarea, and select element inside the form. The data is sent the same way a browser sends a regular form — as URL-encoded form data in the POST body. The server reads it using its standard form-parsing tools.

Field TypeValue Sent
text inputWhatever the user typed
checkbox (checked)The value attribute, or "on"
checkbox (unchecked)Not sent at all
radio button (selected)The value of the selected option
selectThe selected option's value
hidden inputIts value attribute

Replacing the Form on Success

A common pattern is to replace the entire form with a success message after submission. Use hx-swap="outerHTML" on the form and have the server return a confirmation div:

<form
  id="contact-form"
  hx-post="/contact"
  hx-target="#contact-form"
  hx-swap="outerHTML">
  ...
  <button type="submit">Send</button>
</form>

Server response on success:

<div id="contact-form">
  <h3>Message sent!</h3>
  <p>We will get back to you within 24 hours.</p>
</div>

The form disappears and the thank-you message takes its place — without a page reload.

Showing Server-Side Validation Errors

When the server finds validation errors, it returns the form HTML again — this time with error messages filled in. HTMX replaces the old form with the error version, and the user sees exactly what needs fixing.

Server response on validation failure (HTTP 422 Unprocessable Entity):

<form id="contact-form" hx-post="/contact" hx-target="#contact-form" hx-swap="outerHTML">

  <label>Name</label>
  <input type="text" name="name" value="John">

  <label>Email</label>
  <input type="email" name="email" value="not-an-email">
  <span style="color:red">Please enter a valid email address.</span>

  <label>Message</label>
  <textarea name="message"></textarea>
  <span style="color:red">Message cannot be empty.</span>

  <button type="submit">Send</button>
</form>

The user's existing input (name, email value) is preserved. Only the errors are new. This is far friendlier than a blank form after a failed submit.

Using hx-vals to Add Hidden Data

You can inject additional data into a form submission without a hidden input field:

<form
  hx-post="/order"
  hx-vals='{"product_id": 99, "source": "homepage"}'
  hx-target="#order-result">
  <input type="number" name="quantity" value="1">
  <button type="submit">Order Now</button>
</form>

The POST body includes quantity, product_id=99, and source=homepage. The extra values from hx-vals merge seamlessly with the form inputs.

Multi-Step Forms

HTMX makes multi-step forms simple. Each step is a separate server route that returns the next step's HTML. The form container updates in place between steps:

<!-- Step 1 -->
<div id="wizard">
  <form hx-post="/wizard/step1" hx-target="#wizard" hx-swap="innerHTML">
    <h3>Step 1: Personal Details</h3>
    <input type="text" name="first_name" placeholder="First name">
    <input type="text" name="last_name" placeholder="Last name">
    <button type="submit">Next →</button>
  </form>
</div>

Server returns step 2 after step 1 submits:

<form hx-post="/wizard/step2" hx-target="#wizard" hx-swap="innerHTML">
  <h3>Step 2: Address</h3>
  <input type="hidden" name="first_name" value="John">
  <input type="hidden" name="last_name" value="Smith">
  <input type="text" name="street" placeholder="Street address">
  <button type="submit">Next →</button>
</form>

The server carries data forward using hidden inputs or server-side session storage. The user sees the wizard progress inside the same div.

Disabling the Submit Button During Request

<style>
  form.htmx-request button[type="submit"] {
    opacity: 0.6;
    pointer-events: none;
  }
</style>

When HTMX sends the request, it adds htmx-request to the form. The CSS rule grays out and disables the submit button automatically. No JavaScript needed.

Key Takeaway

HTMX form submission intercepts the submit event, collects all field values, and sends a POST request in the background. The server processes the data and returns either a success fragment or a re-rendered form with error messages. The page never reloads. Users get instant feedback, preserved scroll position, and a smoother experience than traditional form-and-redirect flows.

Leave a Comment

Your email address will not be published. Required fields are marked *