HTMX
HTMX is a HTML-over-the-wire JavaScript library that extends HTML with attributes to facilitate AJAX requests, WebSocket connections, CSS Transitions, and server-sent events directly in HTML.
Core Attributes
hx-get issues a GET to the specified URL
hx-post issues a POST to the specified URL
hx-on* handle events with inline scripts on elements
hx-push-url push a URL into the browser location bar to create history
hx-select select content to swap in from a response
hx-select-oob select content to swap in from a response, somewhere other than the target (out of band)
hx-swap controls how content will swap in (outerHTML, beforeend, afterend, …)
hx-swap-oob mark element to swap in from a response (out of band)
hx-target specifies the target element to be swapped
hx-trigger specifies the event that triggers the request
hx-vals add values to submit with the request (JSON format)
Swapping with hx-swap=...
innerHTML:puts the content inside target element (default)outerHTML: replaces entire target element with returned contentafterbegin: prepends content before first child inside the targetbeforebegin:prepends content before target in target's parent elementbeforeend: appends content after last child inside targetafterend: appends content after target in target's parent elementdelete: deletes target element regardless of responsenone: does nothing (oob-swaps and Response Headers are still processed)
Swap Options (e.g. hx-swap="outerHTML ignoreTitle:true"):
transition: true/false, whether to use view transition API for this swapswap: swap delay (e.g. 100ms) between clearing old and inserting newsettle: settle delay (e.g. 100ms) between inserting new and settlingignoreTitle: if true, titles in new content will be ignoredscroll: top/bottom, will scroll the target element to its top or bottomshow: top/bottom, will scroll target element's top or bottom into view
HTMX and Go
<!DOCTYPE html> <html> <head> <title>htmx example</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"> <script src="https://unpkg.com/htmx.org@1.9.2"></script> </head> <body data-bs-theme="dark"> <ul class="list-group" id="film-list"> {{ range . }} {{ block "film-list-element" . }} <li class="list-group-item"> {{ .Title }} - {{ .Director }} </li> {{ end }} {{ end }} </ul> <!-- form to add a film to existing list --> <form hx-post="/add" hx-target="#film-list" hx-swap="beforeend"> <div class="mb-2"> <label for="title">Title</label> <input type="text" class="form-control" id="title" name="title"> </div> <div class="mb-3"> <label for="director">Director</label> <input type="text" class="form-control" id="director" name="director"> </div> <button type="submit" class="btn btn-primary">Add Film</button> </form> </body> </html>
package main
import (
"log"
"net/http"
"html/template"
)
type Film struct {
Title string
Director string
}
func main() {
log.Println("Server started on: http://localhost:8000")
tmpl := template.Must(template.ParseFiles("index.html"))
films := map[string] Film {
"The Shawshank Redemption": {"The Shawshank Redemption", "Frank Darabont"},
"The Godfather": {"The Godfather", "Francis Ford Coppola"},
}
http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) {
tmpl.Execute(w, films)
})
http.HandleFunc("/add", func (w http.ResponseWriter, r *http.Request) {
log.Println("htmx Add request received")
log.Println(r.Header.Get("HX-Request"))
if r.Method == "POST" {
title := r.PostFormValue("title")
director := r.PostFormValue("director")
tmpl.ExecuteTemplate(w, "film-list-element", Film{title, director})
}
})
log.Fatal(http.ListenAndServe(":8000", nil))
}
Libraries:
Web Security Guidelines
Only call routes you control
- Uncontrolled routes could insert malicious
<script>tags - Only use relative URLs:
hx-get="/events"
Always use an auto-escaping template engine
- Replaces &, <, >, ", … with "&", "<", etc.
- Prevents Cross-Site Scripting (XSS) attacks
Only serve user-generated content inside HTML tags
- No user-defined data in scripts, CSS, or as HTML attributes
Set authentication cookies with Secure, HttpOnly, and SameSite=Lax
Secure: only send the cookie via HTTPSHttpOnly: don't make the cookie available to JS viadocument.cookieSameSite=Lax: mitigate Cross-Site Request Forgery (CSRF)