Gothic Lady Logo GOTTH

Oob Swaps

Out-of-Band Swaps

Normal HTMX works like this: a user clicks a button, HTMX sends a request to the server, the server returns HTML, and HTMX swaps that HTML into the DOM where the hx-target says to put it. One request, one target.

Out-of-band (OOB) swaps let you return HTML for multiple targets in a single response. The main response goes to the primary target. Additional elements marked with hx-swap-oob="true" are automatically swapped into their matching DOM elements elsewhere on the page. One request, many targets.

When to Use This

  • Updating a cart count badge when the user adds an item
  • Refreshing a notification indicator alongside the main content
  • Updating a sidebar and the main content area in one round trip
  • Showing a flash message while the main content changes

How It Works

The server returns the primary content as usual, but appends extra elements with hx-swap-oob="true":

<!-- Primary target content -->
<div id="cart-contents">
    <div class="cart-item">Widget — $12.99</div>
    <div class="cart-total">Total: $12.99</div>
</div>

<!-- OOB update: cart badge in the header -->
<span id="cart-badge" hx-swap-oob="true">1</span>

<!-- OOB update: flash message -->
<div id="flash-message" hx-swap-oob="true">
    Added to cart
</div>

HTMX parses the entire response, finds elements with hx-swap-oob="true", and swaps them into the DOM by id matching. The swap can be innerHTML (default), outerHTML, beforebegin, afterbegin, beforeend, or afterend — same as regular hx-swap.

Server-Side Implementation

In Go, you render the primary content and append OOB elements. Since everything is server-rendered, you have full control over what appears in the response:

func CartHandler(w http.ResponseWriter, r *http.Request) {
    cart := getCart(r)
    count := len(cart.Items)

    // Render primary content
    w.Write([]byte(`<div id="cart-contents">`))
    for _, item := range cart.Items {
        fmt.Fprintf(w, `<div class="cart-item">%s — $%.2f</div>`,
            item.Name, item.Price)
    }
    fmt.Fprintf(w, `<div class="cart-total">Total: $%.2f</div>`, cart.Total)
    w.Write([]byte(`</div>`))

    // Render OOB badge
    fmt.Fprintf(w,
        `<span id="cart-badge" hx-swap-oob="true">%d</span>`, count)

    // Render OOB flash
    if cart.JustAdded {
        fmt.Fprintf(w,
            `<div id="flash-message" hx-swap-oob="true">Added to cart</div>`)
    }
}

In practice you'd use Templ components rather than string concatenation:

templ CartResponse(cart Cart) {
	<div id="cart-contents">
		for _, item := range cart.Items {
			<div class="cart-item">
				{ item.Name } — { fmt.Sprintf("$%.2f", item.Price) }
			</div>
		}
		<div class="cart-total">Total: { fmt.Sprintf("$%.2f", cart.Total) }</div>
	</div>
	if cart.Count > 0 {
		<span id="cart-badge" hx-swap-oob="true">
			{ cart.Count }
		</span>
	}
}

Multiple OOB Elements

You can include as many OOB elements as you want. Each must have a unique id matching an existing element on the page. If an OOB element's id doesn't exist in the DOM, HTMX logs a warning and skips it. No crash, no error — just a console message that's easy to miss during development.

Swapping Strategies

<!-- innerHTML (default) — replaces content inside the target -->
<span id="badge" hx-swap-oob="true">5</span>

<!-- outerHTML — replaces the target element itself -->
<div id="toast" hx-swap-oob="outerHTML">
    <div class="toast toast--success">Saved</div>
</div>

<!-- beforeend — appends inside the target -->
<li id="log" hx-swap-oob="beforeend">New log entry</li>

outerHTML is useful when you want to change the element's tag or attributes, not just its content. beforeend is useful for appending to lists. delete removes the target element entirely:

<div id="spinner" hx-swap-oob="delete"></div>

This removes the spinner from the DOM after the request completes, no additional JavaScript needed.

OOB vs SSE

OOB swaps are for one-shot updates tied to a specific user action. If you need real-time updates pushed from the server (chat messages, live notifications, stock tickers), use HTMX's SSE or WebSocket extensions instead. OOB swaps require a trigger action — a button click, a form submission, a polling interval. They don't happen on their own.