Skip to content

Add zio-blocks-htmx — Type-Safe HTMX Integration for zio-blocks-template #1214

@987Nabil

Description

@987Nabil

Summary

Add a zio-blocks-htmx module that provides a type-safe, ergonomic Scala DSL for all htmx HTML attributes — matching or exceeding the quality level of the existing zio-http-datastar-sdk.

Depends on zio-blocks-template (#1164 / #996).

Motivation

The current zio-http-htmx module is extremely primitive — it wraps every htmx attribute as PartialAttribute[String], provides zero type safety, no modifier support, no builder patterns, and has exactly 1 test. Meanwhile, the zio-http-datastar-sdk in the same repo provides a fully typed DSL with modifier ADTs, builder patterns, signal types, and comprehensive tests.

Problems with the current zio-http htmx module:

Problem Current State Expected State
Swap strategies hxSwapAttr := "innerHTML" (string) hxSwap := HxSwap.InnerHTML.swap(1.second).settle(500.millis)
Trigger modifiers hxTriggerAttr := "click delay:500ms" (string) hxTrigger := HxTrigger.click.delay(500.millis).once
Event handlers ~100 boilerplate hxOn*Attr defs hxOn.click := js"alert('hi')" builder
Target selectors hxTargetAttr := "closest div" (string) hxTarget := HxTarget.closest("div")
JSON vals hxValsAttr := """{"key":"value"}""" (string) hxVals := HxVals.from[MyCase](value) with Schema
Tests 1 test Comprehensive
Template version Old template New zio-blocks-template

Scope

In Scope (zio-blocks-htmx)

Core HTTP Method Attributes — typed URL values:

  • hxGet, hxPost, hxPut, hxPatch, hxDelete

Swap StrategyHxSwap sealed trait/enum with modifiers:

sealed trait HxSwap {
  def swap(duration: Duration): HxSwap      // swap:1s
  def settle(duration: Duration): HxSwap    // settle:500ms
  def transition: HxSwap                    // transition:true
  def scroll(position: ScrollPosition): HxSwap
  def show(position: ShowPosition): HxSwap
  def ignoreTitle: HxSwap
  def focusScroll(enabled: Boolean): HxSwap
}
object HxSwap {
  case object InnerHTML extends HxSwap      // innerHTML (default)
  case object OuterHTML extends HxSwap      // outerHTML
  case object TextContent extends HxSwap    // textContent
  case object BeforeBegin extends HxSwap    // beforebegin
  case object AfterBegin extends HxSwap     // afterbegin
  case object BeforeEnd extends HxSwap      // beforeend
  case object AfterEnd extends HxSwap       // afterend
  case object Delete extends HxSwap         // delete
  case object None extends HxSwap           // none
}

Trigger BuilderHxTrigger with modifier chains:

HxTrigger.click                            // "click"
HxTrigger.click.delay(500.millis)          // "click delay:500ms"
HxTrigger.click.throttle(1.second)         // "click throttle:1s"
HxTrigger.click.once.changed               // "click once changed"
HxTrigger.click.from("#other")             // "click from:#other"
HxTrigger.load                             // "load"
HxTrigger.revealed                         // "revealed"
HxTrigger.intersect.threshold(0.5)         // "intersect threshold:0.5"
HxTrigger.every(2.seconds)                 // "every 2s"
HxTrigger.click.filter(js"ctrlKey")        // "click[ctrlKey]"
HxTrigger.click.queue(QueueStrategy.Last)  // "click queue:last"
// Multiple triggers
HxTrigger(HxTrigger.load, HxTrigger.click.delay(1.second))

Target SelectorsHxTarget with extended selectors:

HxTarget.this_                             // "this"
HxTarget.closest("div")                    // "closest div"
HxTarget.find(".result")                   // "find .result"
HxTarget.next                              // "next"
HxTarget.next(".sibling")                  // "next .sibling"
HxTarget.previous                          // "previous"
HxTarget.css("#my-id")                     // "#my-id" (regular CSS selector)

Event Handler BuilderhxOn (NOT 100 defs):

hxOn.click := js"doSomething()"            // hx-on:click="doSomething()"
hxOn.submit := js"validate()"             // hx-on:submit="validate()"
hxOn("custom-event") := js"handle()"      // hx-on:custom-event="handle()"
// Uses Js type from zio-blocks-template for the expression

Other Typed Attributes:

  • hxBoost — boolean attribute
  • hxPushUrl / hxReplaceUrl — boolean or URL
  • hxSelect / hxSelectOob — CSS selector
  • hxSwapOob — boolean or swap strategy
  • hxConfirm / hxPrompt — string
  • hxDisable — boolean
  • hxDisabledElt — CSS selector
  • hxIndicator — CSS selector
  • hxInclude — CSS selector with extended selectors
  • hxParamsHxParams enum (All, None, Not(params), Only(params))
  • hxSyncHxSync strategy type
  • hxValidate — boolean
  • hxPreserve — boolean
  • hxEncodingHxEncoding enum (Multipart)
  • hxExt — extension name
  • hxHeaders — JSON (with Schema integration like HxVals)
  • hxDisinherit — attribute name list
  • hxHistory — boolean

Schema Integration:

  • hxVals with Schema[A] for type-safe JSON encoding
  • hxHeaders with same pattern

Also In Scope (via http-model dependency)

Since zio-blocks already has http-model with Header, Headers, Request, Response:

Response Headers — typed htmx response header values:

  • HX-Location (JSON — client-side redirect with swap options)
  • HX-Push-Url / HX-Replace-Url (URL)
  • HX-Redirect (URL — full page redirect)
  • HX-Refresh (boolean)
  • HX-Reswap (swap strategy — reuses HxSwap type)
  • HX-Retarget (CSS selector)
  • HX-Reselect (CSS selector)
  • HX-Trigger / HX-Trigger-After-Settle / HX-Trigger-After-Swap (JSON/string — trigger client events)

Request Headers — typed htmx request header parsing:

  • HX-Request (boolean — always sent by htmx)
  • HX-Boosted (boolean)
  • HX-Current-URL (URL)
  • HX-Target (element ID)
  • HX-Trigger / HX-Trigger-Name (element ID/name)
  • HX-History-Restore-Request (boolean)
  • HX-Prompt (string — user response to hx-prompt)

These integrate directly with http-model's Header.Typed[H] pattern.

Out of Scope (belongs in zio-http)

  • Endpoint integration — generating htmx URLs from Endpoint definitions
  • SSE/WebSocket extensions — require zio-http transport

Design Principles

  1. Match datastar SDK quality — the datastar-sdk in zio-http is the reference for API design quality.
  2. Use zio-blocks-template typesDom, PartialAttribute, Js from PR feat(template): zio-blocks-template — type-safe HTML templating with compile-time optimizations #1164.
  3. Zero additional dependencies — only depend on zio-blocks-template and zio-blocks-schema (for JSON vals).
  4. Cross-platform — JVM + JS.
  5. Render to valid htmx — all typed values must render to correct htmx attribute strings.

Example Usage (Target API)

import zio.blocks.template._
import zio.blocks.htmx._

val searchForm = form(
  hxPost := "/search",
  hxTarget := HxTarget.find("#results"),
  hxSwap := HxSwap.InnerHTML.settle(200.millis),
  hxTrigger := HxTrigger.submit,
  hxIndicator := css"#spinner"
)(
  input(
    typeAttr := "text",
    nameAttr := "q",
    hxGet := "/suggest",
    hxTrigger := HxTrigger("input").changed.delay(300.millis),
    hxTarget := HxTarget.next(".suggestions")
  ),
  button(typeAttr := "submit")("Search")
)

val infiniteScroll = div(
  hxGet := s"/items?page=$nextPage",
  hxTrigger := HxTrigger.revealed,
  hxSwap := HxSwap.AfterEnd,
  hxIndicator := css".spinner"
)("Loading...")

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions