htmx

package
v0.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 1, 2026 License: MIT Imports: 8 Imported by: 0

README

platform/htmx: HTMX Frontend Helpers

Package htmx provides idiomatic Go helpers for building modern, server-rendered web frontends with HTMX on top of the Sling platform.


1. Design & Philosophy

  • Zero External Dependencies: Implemented entirely using Go standard library (net/http, encoding/json, sync, io). Zero impact on dependency footprint.
  • Template Engine Agnostic: Works out of the box with standard html/template, text/template, or any engine implementing ExecuteTemplate. (For a-h/templ, invoke its native Component.Render(ctx, w) directly or use htmx.HTML).
  • Strict Specification Compliance: Explicitly separates request reading (TriggerID, TriggerName) from response event triggers (Trigger, TriggerPayload) to prevent naming collisions.
  • Automatic Event Merging: Multiple trigger calls on the same response safely merge into a valid JSON object without clobbering earlier headers.
  • Buffer-Protected Rendering: htmx.Render executes templates into an in-memory buffer pool before flushing to the client, preventing premature 200 OK status codes or corrupted HTML fragments on template execution errors.

2. Request Inspection

HTMX transmits context through special request headers. Inspect them easily using type-safe helpers:

Request Header Helper Function Description
HX-Request htmx.IsHTMX(r) bool true if the request was issued by HTMX
HX-Boosted htmx.IsBoosted(r) bool true if the request was triggered via hx-boost
HX-History-Restore-Request htmx.IsHistoryRestore(r) bool true if restoring page state from the local history cache
HX-Current-URL htmx.CurrentURL(r) string The current URL of the browser when the request was initiated
HX-Prompt htmx.Prompt(r) string User input entered in response to an hx-prompt dialog
HX-Target htmx.Target(r) string The ID of the target element intended to receive the swap
HX-Trigger htmx.TriggerID(r) string The ID of the element that triggered the request
HX-Trigger-Name htmx.TriggerName(r) string The name attribute of the element that triggered the request
func handleSearch(w http.ResponseWriter, r *http.Request) {
    if !htmx.IsHTMX(r) {
        // Direct browser navigation: render full page layout with header & footer
        renderFullPage(w, r)
        return
    }

    // HTMX partial request: render only table rows
    renderSearchResults(w, r)
}

3. Response Control: Navigation & Swapping

Control client-side navigation, history entries, and swap targets without custom JavaScript:

Navigation & History
  • htmx.Redirect(w, "/login") — Instructs the browser to perform a full client-side redirect (HX-Redirect).
  • htmx.Refresh(w) — Instructs the browser to do a full page reload (HX-Refresh: "true").
  • htmx.Location(w, "/dashboard") — Navigates without a full reload (HX-Location).
  • htmx.PushURL(w, "/items?page=2") — Pushes a new URL into the browser history stack (HX-Push-Url).
  • htmx.PreventPushURL(w) — Explicitly prevents URL updates (HX-Push-Url: "false").
  • htmx.ReplaceURL(w, "/items/42") — Replaces current URL in location bar without push (HX-Replace-Url).
  • htmx.PreventReplaceURL(w) — Explicitly prevents URL replacement (HX-Replace-Url: "false").
Swap Modifiers
  • htmx.Reswap(w, htmx.SwapOuterHTML) — Overrides the swap strategy (HX-Reswap). Available constants: SwapInnerHTML, SwapOuterHTML, SwapBeforeBegin, SwapAfterBegin, SwapBeforeEnd, SwapAfterEnd, SwapDelete, SwapNone.
  • htmx.Retarget(w, "#modal-container") — Overrides the target element using a CSS selector (HX-Retarget).
  • htmx.Reselect(w, ".card-body") — Selects a subset of response HTML to swap (HX-Reselect).

4. Client-Side Event Triggers

HTMX allows servers to trigger custom events in the browser via headers. The htmx package supports single string events, event payloads, and multiple events simultaneously.

Trigger Helper Functions
Phase / Header Single Event Event with Payload Bulk Map (map[string]any)
Immediate (HX-Trigger) htmx.Trigger(w, event) htmx.TriggerPayload(w, event, payload) htmx.TriggerEvents(w, events)
After Swap (HX-Trigger-After-Swap) htmx.TriggerAfterSwap(w, event) htmx.TriggerAfterSwapPayload(w, event, payload) htmx.TriggerAfterSwapEvents(w, events)
After Settle (HX-Trigger-After-Settle) htmx.TriggerAfterSettle(w, event) htmx.TriggerAfterSettlePayload(w, event, payload) htmx.TriggerAfterSettleEvents(w, events)
  • Immediate: Fires as soon as the response is received by the client.
  • After Swap: Fires after the DOM content has been swapped.
  • After Settle: Fires after DOM settlement and CSS transitions finish.
Automatic Event Merging

Calling multiple trigger helpers on the same http.ResponseWriter automatically aggregates them into a valid JSON map:

func handleCreateItem(w http.ResponseWriter, r *http.Request) {
    // 1. Simple event trigger
    htmx.Trigger(w, "itemCreated")

    // 2. Event with payload
    htmx.TriggerPayload(w, "showToast", map[string]string{
        "type":    "success",
        "message": "Item saved successfully",
    })

    // Resolves automatically to:
    // HX-Trigger: {"itemCreated":null,"showToast":{"type":"success","message":"Item saved successfully"}}

    htmx.HTML(w, http.StatusOK, `<tr id="item-42"><td>Item 42</td></tr>`)
}

Alternatively, emit multiple events in a single call using TriggerEvents:

htmx.TriggerEvents(w, map[string]any{
    "itemCreated": map[string]any{"id": 42, "name": "Widget"},
    "closeModal":  true,
    "refreshFeed": nil,
})

In the browser, listen to events directly or with Alpine.js:

<body hx-on:item-created="console.log('Created!')"
      hx-on:show-toast="showNotification(event.detail)">

5. Rendering: Templates & HTML

htmx.Render(w, status, tmpl, name, data)

Executes a named template from *html/template.Template or any implementation of htmx.Template.

var tmpl = template.Must(template.ParseGlob("web/templates/*.html"))

func handleGetUsers(w http.ResponseWriter, r *http.Request) {
    users := []User{{Name: "Alice"}, {Name: "Bob"}}

    if htmx.IsHTMX(r) {
        // Return only the partial rows fragment
        _ = htmx.Render(w, http.StatusOK, tmpl, "user_rows.html", users)
        return
    }

    // Return the full page shell enclosing user_rows.html
    _ = htmx.Render(w, http.StatusOK, tmpl, "layout.html", users)
}
Why Buffered Execution Matters:

Standard Go tmpl.Execute(w, data) writes directly to the network socket. If execution encounters a template error halfway through, Go has already sent 200 OK and a partial, broken HTML stream.
htmx.Render renders into a pooled bytes.Buffer first. If execution fails, it aborts cleanly and returns the error without writing any corrupted bytes or premature headers to w.

htmx.HTML(w, status, html)

For small HTML fragments, badges, or button state updates:

htmx.HTML(w, http.StatusOK, `<span class="badge bg-success">Saved</span>`)

6. End-to-End Pattern: Full Page vs Partial Fragment

A canonical Sling pattern for building responsive HTMX applications with zero client framework overhead:

Browser Request
      │
      ▼
┌───────────────┐
│ htmx.IsHTMX?  │
└───┬───────┬───┘
    │       │
    │ Yes   │ No (Direct navigation / Bookmark / F5)
    ▼       ▼
┌────────┐ ┌────────────────┐
│ Render │ │  Render Full   │
│ Partial│ │ Page with Base │
│ (Rows) │ │  HTML Shell    │
└────────┘ └────────────────┘
Handler Implementation
package web

import (
	"html/template"
	"net/http"

	"github.com/blessed-go/sling/platform/htmx"
)

type Handler struct {
	tmpl *template.Template
}

func (h *Handler) ListItems(w http.ResponseWriter, r *http.Request) {
	items := fetchItems(r.Context())

	// If HTMX triggered this request, return only the rows fragment
	if htmx.IsHTMX(r) {
		if err := htmx.Render(w, http.StatusOK, h.tmpl, "items_list.html", items); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
		return
	}

	// Full page load: wrap with base shell containing <html>, <head>, HTMX script, and navigation
	data := map[string]any{
		"Title": "Inventory Management",
		"Items": items,
	}
	if err := htmx.Render(w, http.StatusOK, h.tmpl, "index.html", data); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

Documentation

Index

Constants

View Source
const (
	HeaderRequest               = "HX-Request"
	HeaderBoosted               = "HX-Boosted"
	HeaderCurrentURL            = "HX-Current-URL"
	HeaderHistoryRestoreRequest = "HX-History-Restore-Request"
	HeaderPrompt                = "HX-Prompt"
	HeaderTarget                = "HX-Target"
	HeaderTrigger               = "HX-Trigger"
	HeaderTriggerName           = "HX-Trigger-Name"
)

HTMX request header constants.

View Source
const (
	HeaderLocation                   = "HX-Location"
	HeaderPushURL                    = "HX-Push-Url"
	HeaderReplaceURL                 = "HX-Replace-Url"
	HeaderRedirect                   = "HX-Redirect"
	HeaderRefresh                    = "HX-Refresh"
	HeaderReswap                     = "HX-Reswap"
	HeaderRetarget                   = "HX-Retarget"
	HeaderReselect                   = "HX-Reselect"
	HeaderResponseTrigger            = "HX-Trigger"
	HeaderResponseTriggerAfterSwap   = "HX-Trigger-After-Swap"
	HeaderResponseTriggerAfterSettle = "HX-Trigger-After-Settle"
)

HTMX response header constants.

Variables

This section is empty.

Functions

func CurrentURL

func CurrentURL(r *http.Request) string

CurrentURL returns the browser's current URL when the request was made.

func HTML

func HTML(w http.ResponseWriter, status int, html string)

HTML writes a raw HTML string to the response writer with the given HTTP status code.

func IsBoosted

func IsBoosted(r *http.Request) bool

IsBoosted reports whether the request was boosted via hx-boost.

func IsHTMX

func IsHTMX(r *http.Request) bool

IsHTMX reports whether the incoming request was issued by HTMX.

func IsHistoryRestore

func IsHistoryRestore(r *http.Request) bool

IsHistoryRestore reports whether the request is for history restoration after a cache miss.

func Location

func Location(w http.ResponseWriter, pathOrJSON string)

Location instructs HTMX to perform a client-side navigation without full page reload (HX-Location).

func PreventPushURL

func PreventPushURL(w http.ResponseWriter)

PreventPushURL prevents updating the browser history URL (HX-Push-Url: "false").

func PreventReplaceURL

func PreventReplaceURL(w http.ResponseWriter)

PreventReplaceURL prevents replacing the browser location bar (HX-Replace-Url: "false").

func Prompt

func Prompt(r *http.Request) string

Prompt returns the user response entered into an hx-prompt dialog.

func PushURL

func PushURL(w http.ResponseWriter, url string)

PushURL pushes a new URL into the browser history stack (HX-Push-Url).

func Redirect

func Redirect(w http.ResponseWriter, url string)

Redirect instructs HTMX to perform a client-side redirect to the given URL (HX-Redirect).

func Refresh

func Refresh(w http.ResponseWriter)

Refresh instructs HTMX to perform a full page refresh (HX-Refresh: "true").

func Render

func Render(w http.ResponseWriter, status int, tmpl Template, name string, data any) error

Render executes a template into an in-memory buffer before writing to w. This guarantees that template execution errors return an error before any 200 OK headers or partial broken HTML are flushed to the client.

func ReplaceURL

func ReplaceURL(w http.ResponseWriter, url string)

ReplaceURL replaces the current URL in the browser location bar (HX-Replace-Url).

func Reselect

func Reselect(w http.ResponseWriter, selector string)

Reselect selects a subset of the response HTML using a CSS selector (HX-Reselect).

func Reswap

func Reswap(w http.ResponseWriter, strategy SwapStrategy)

Reswap sets how the response will be swapped into the target element (HX-Reswap).

func Retarget

func Retarget(w http.ResponseWriter, selector string)

Retarget overrides the target of the swap with a CSS selector (HX-Retarget).

func Target

func Target(r *http.Request) string

Target returns the ID of the target element (HX-Target header).

func Trigger

func Trigger(w http.ResponseWriter, event string)

Trigger fires a client-side event immediately upon receiving the response (HX-Trigger). If multiple events are triggered on the same response, they are merged into a JSON map.

func TriggerAfterSettle

func TriggerAfterSettle(w http.ResponseWriter, event string)

TriggerAfterSettle fires a client-side event after the swap settles (HX-Trigger-After-Settle).

func TriggerAfterSettleEvents

func TriggerAfterSettleEvents(w http.ResponseWriter, events map[string]any)

TriggerAfterSettleEvents fires multiple events after the swap settles.

func TriggerAfterSettlePayload

func TriggerAfterSettlePayload(w http.ResponseWriter, event string, payload any)

TriggerAfterSettlePayload fires a client-side event with a payload after the swap settles.

func TriggerAfterSwap

func TriggerAfterSwap(w http.ResponseWriter, event string)

TriggerAfterSwap fires a client-side event after the swap step (HX-Trigger-After-Swap).

func TriggerAfterSwapEvents

func TriggerAfterSwapEvents(w http.ResponseWriter, events map[string]any)

TriggerAfterSwapEvents fires multiple events after the swap step.

func TriggerAfterSwapPayload

func TriggerAfterSwapPayload(w http.ResponseWriter, event string, payload any)

TriggerAfterSwapPayload fires a client-side event with a payload after the swap step.

func TriggerEvents

func TriggerEvents(w http.ResponseWriter, events map[string]any)

TriggerEvents fires multiple client-side events simultaneously via a JSON map (HX-Trigger).

func TriggerID

func TriggerID(r *http.Request) string

TriggerID returns the ID of the element that triggered the request (HX-Trigger header).

func TriggerName

func TriggerName(r *http.Request) string

TriggerName returns the name of the element that triggered the request (HX-Trigger-Name header).

func TriggerPayload

func TriggerPayload(w http.ResponseWriter, event string, payload any)

TriggerPayload fires a client-side event with a payload upon receiving the response (HX-Trigger).

Types

type SwapStrategy

type SwapStrategy string

SwapStrategy defines HTMX swap behaviors for HX-Reswap.

const (
	SwapInnerHTML   SwapStrategy = "innerHTML"
	SwapOuterHTML   SwapStrategy = "outerHTML"
	SwapBeforeBegin SwapStrategy = "beforebegin"
	SwapAfterBegin  SwapStrategy = "afterbegin"
	SwapBeforeEnd   SwapStrategy = "beforeend"
	SwapAfterEnd    SwapStrategy = "afterend"
	SwapDelete      SwapStrategy = "delete"
	SwapNone        SwapStrategy = "none"
)

type Template

type Template interface {
	ExecuteTemplate(wr io.Writer, name string, data any) error
}

Template represents a template capable of executing a named template. Both standard *html/template.Template and *text/template.Template satisfy this interface.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL