Htmx allows us to handle async form submissions with ease by using hx-post attribute. In background it will issue a post request to the url specified in hx-post attribute and replace the element with the response.

Setup with Gofiber

Initialize go module and install dependencies

go mod init htmx-form
go get github.com/gofiber/fiber/v2
go get github.com/gofiber/template/html/v2

Setup Server

package main

import (
    "log"

	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/log"
	"github.com/gofiber/template/html/v2"
)

func main() {
    // Setup html template engine
	engine := html.New("./views", ".html")

	app := fiber.New(fiber.Config{
		Views: engine,
	})

    // Get request to render index page
	app.Get("/", func(c *fiber.Ctx) error {
		values := fiber.Map{
			"EmailErr":    "",
			"PasswordErr": "",
			"Success":     "",
			"Error":       "",
		}
		// Render index template
		return c.Render("index", values)
	})

    // Post request to handle form submission
	app.Post("/form", func(c *fiber.Ctx) error {

        // Get form values
		email := c.FormValue("email")
		password := c.FormValue("password")

        // Setup values to be passed to template
		values := fiber.Map{
			"EmailValue":  email,
			"EmailErr":    "",
			"PasswordErr": "",
			"Success":     "",
			"Error":       "",
		}

        // Validate form values
		if email == "" {
			values["EmailErr"] = "Email is required"
		}
		if password == "" {
			values["PasswordErr"] = "Password is required"
		}
		if email != "" && password != "" {
			values["EmailValue"] = ""
			values["Success"] = "Validation success"
		} else {
			values["Error"] = "Validation error"
		}

		// Render index template
		return c.Render("form", values)
	})

	log.Fatal(app.Listen(":3000"))
}

Setup Views

views/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Bootstrap demo</title>
    <!-- bootstrap -->
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN"
      crossorigin="anonymous"
    />
  </head>
  <body>
    <main class="container pt-5">{{ template "form" . }}</main>
    <!-- bootstrap -->
    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
      integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
      crossorigin="anonymous"
    ></script>
    <!-- htmx -->
    <script src="https://unpkg.com/htmx.org@1.9.10" integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC" crossorigin="anonymous"></script>
  </body>
</html>

views/form.html

form template with rendering logic

<form
  id="user-form"
  hx-post="/form"
  autocomplete="off"
>
  {{ if ne .Success "" }}
  <div class="alert alert-success" role="alert">{{.Success}}</div>
  {{ end }} {{ if ne .Error "" }}
  <div class="alert alert-danger" role="alert">{{.Error}}</div>
  {{ end }}
  <div class="mb-3">
    <label for="exampleInputEmail1" class="form-label">Email address</label>
    <input
      type="email"
      class="form-control{{ if ne .EmailErr "" }} is-invalid{{ end }}"
      id="exampleInputEmail1"
      aria-describedby="emailHelp"
      name="email"
      value="{{.EmailValue}}"
    />
    <div id="emailHelp" class="form-text">
      We'll never share your email with anyone else.
    </div>
    {{ if ne .EmailErr "" }}
    <div class="invalid-feedback">{{.EmailErr}}</div>
    {{ end }}
  </div>
  <div class="mb-3">
    <label for="exampleInputPassword1" class="form-label">Password</label>
    <input
      type="password"
      name="password"
      class="form-control{{ if ne .PasswordErr "" }} is-invalid{{end}}"
      id="exampleInputPassword1"
    />
    {{ if ne .PasswordErr "" }}
    <div class="invalid-feedback">{{.PasswordErr}}</div>
    {{ end }}
  </div>
  <button type="submit" class="btn btn-primary">Submit</button>
</form>

This setup will render a page with a form. On submitting the form, it will validate the form values and render the form with validation errors or success message.