wbunting

A simple but powerful HTMX pattern

January 20, 2024 (3mo ago)

I have recently been toying with HTMX (I suppose many people have been). Perhaps I will write more in depth about it at a future time. Succinctly I think the thing it gets correct is server rendering HTML rather than sending JSON and allowing the client to do the rendering. React server components basically is the same realization as this. It's nice to not have to define a JSON api in addition to deciding the rendering logic. It reduces the surface area for bugs by reducing the need to resolve differences between the server / client state.

Keeping this short. A powerful pattern I have been liking is to construct templates for HTMX "components" that recieve arbitrary HTMX props. This makes them more flexible when used in composition. Eg.

package main

templ iconButton(htmxAttrs templ.Attributes, iconName *string) {
  <button 
    class="px-[0.5] rounded-md hover:bg-slate-300"
    { htmxAttrs... }
  >
    @icon(*iconName)
  </button>
}

// and you use this like
<div class="hidden group-hover:block">
  @iconButton(
    templ.Attributes{ 
      "hx-delete": fmt.Sprintf("/api/pages/%s", page.ID), 
      "hx-target": fmt.Sprintf("#page-item-%s", page.ID) 
    }, 
    strPtr("trash")
  )
</div>

This allows for the Button to be a purely UI concern, leaving the logic handling to the caller.

This pattern is pretty popular in React component libraries. Eg. there are a set of "atoms" basic components that don't really do any logic other than allow you to do things like pass through event handlers to the underlying HTML elements. Then more complex "molecules" are components that are compositions of these individual blocks, that implement or decide what logic the "atoms" will use.

In the example I showed here I'm passing hx-delete because in that case the 'iconButton' is for deleting an item. But it could have just as easily been a form submit button in which we would have wanted 'hx-post'.