HX

Package Version Hex Docs

HX is a comprehensive Gleam package that provides complete HTMX integration for the Lustre web framework. It allows you to easily add HTMX attributes to your Lustre HTML elements, enabling dynamic, AJAX-powered web applications with minimal JavaScript.

Installation

Add hx to your Gleam project:

gleam add hx

Quick Start

import hx
import lustre/element.{button, div, input, text}

pub fn main() {
  div([], [
    // Simple GET request
    button([hx.get("/example")], [text("Load Content")]),
    
    // POST with custom trigger event
    button([
      hx.post("/api/save"), 
      hx.trigger([hx.click_event()])
    ], [text("Save Data")]),
    
    // Input with throttled requests
    input([
      hx.get("/search"), 
      hx.trigger([hx.with_throttle(hx.input_event(), hx.Milliseconds(300))])
    ], [])
  ])
}

Core Features

๐ŸŒ HTTP Method Attributes

Complete support for all HTTP methods:

โšก Event System

Pre-defined Events - Type-safe event functions:

// Common DOM events
hx.click_event()
hx.change_event()
hx.submit_event()
hx.input_event()
hx.focus_event()
hx.blur_event()
// ... and more

// HTMX lifecycle events
hx.htmx_before_request_event()
hx.htmx_after_request_event()
hx.htmx_before_swap_event()
hx.htmx_after_swap_event()
// ... and more

Event Modifiers - Chain modifiers for complex behavior:

hx.click_event()
|> hx.with_delay(hx.Seconds(1))
|> hx.with_once
|> hx.with_throttle(hx.Milliseconds(500))

Intersection Observer - Viewport-based triggers:

hx.trigger([hx.intersect_event(Some("10px"))])
hx.trigger([hx.intersect_once_event(None)])

๐ŸŽฏ Content Targeting & Swapping

Precise control over where and how content is updated:

// Target specific elements
hx.target(hx.CssSelector("#result"))
hx.target(hx.Closest(".card"))
hx.target(hx.This)

// Control swapping behavior
hx.swap(hx.InnerHTML, None)
hx.swap(hx.OuterHTML, Some(hx.Transition(True)))
hx.swap_oob(hx.Beforeend, Some("#log"), Some(hx.Scroll(hx.Bottom)))

๐Ÿ”„ Advanced Request Control

Synchronization:

hx.sync([hx.Drop("#form"), hx.SyncQueue("#queue", hx.First)])

Headers & Values:

hx.headers(json.object([#("Authorization", json.string("Bearer token"))]), False)
hx.vals(json.object([#("user_id", json.string("123"))]), False)

Request Configuration:

hx.request("{timeout:10000, showProgress:true}")
hx.encoding("multipart/form-data")

๐Ÿ›ก๏ธ User Experience Features

Loading States:

hx.indicator(".loading-spinner")
hx.disable_elt([hx.CssSelector("button")])

User Confirmation:

hx.confirm("Are you sure you want to delete this item?")
hx.prompt("Please enter a reason:")

Navigation:

hx.push_url(True)
hx.replace_url_with("/new-path")
hx.boost(True)  // Progressive enhancement

๐Ÿ“ Form Handling

Parameter Control:

hx.params("username,email")  // Include only specific fields
hx.params("not password")    // Exclude sensitive fields
hx.include(hx.CssSelector("#extra-data"))

Validation:

hx.validate(True)  // Enable HTML5 validation
hx.trigger([hx.htmx_validation_failed_event()])

๐ŸŽจ Client-Side Interactivity

Hyperscript Integration:

hx.hyper_script("on click toggle .active on me")
hx.hyper_script("on load wait 3s then add .hidden")

State Preservation:

hx.preserve()  // Keep element state during swaps
hx.history_elt()  // Mark for browser history

๐Ÿ”ง Advanced Configuration

Inheritance Control:

hx.disinherit(["hx-trigger", "hx-target"])
hx.disinherit_all()
hx.inherit(["hx-swap"])
hx.inherit_all()

Extensions:

hx.ext(["client-side-templates", "json-enc"])

Event Examples

Basic Events

// Simple click trigger
hx.trigger([hx.click_event()])

// Multiple events
hx.trigger([hx.click_event(), hx.keyup_event()])

// Custom events
hx.trigger([hx.custom_event("myEvent")])

Event Modifiers

// Delayed execution
hx.trigger([hx.with_delay(hx.click_event(), hx.Seconds(2))])

// Throttled input
hx.trigger([hx.with_throttle(hx.input_event(), hx.Milliseconds(300))])

// Fire only once
hx.trigger([hx.with_once(hx.click_event())])

// Only on value change
hx.trigger([hx.with_changed(hx.input_event())])

// Listen from different element
hx.trigger([hx.with_from(hx.click_event(), hx.Document)])

HTMX Lifecycle Events

// Before request processing
hx.trigger([hx.htmx_before_request_event()])

// After content swap
hx.trigger([hx.htmx_after_swap_event()])

// Error handling
hx.trigger([hx.htmx_response_error_event()])

Polling

// Simple polling
hx.trigger_polling(hx.Seconds(5), None)

// Conditional polling
hx.trigger_polling(hx.Seconds(10), Some("intersect"))

// Load + polling
hx.trigger_load_polling(hx.Seconds(2), "visible")

Real-World Examples

Search with Debouncing

input([
  type_("text"),
  name("search"),
  hx.get("/api/search"),
  hx.trigger([hx.with_throttle(hx.input_event(), hx.Milliseconds(300))]),
  hx.target(hx.CssSelector("#search-results"))
], [])

Infinite Scroll

div([
  hx.get("/api/more-items"),
  hx.trigger([hx.intersect_once_event(None)]),
  hx.swap(hx.Afterend, None),
  hx.target(hx.This)
], [text("Loading more...")])

Form with Loading State

form([
  hx.post("/api/submit"),
  hx.disable_elt([hx.CssSelector("button")]),
  hx.indicator(".loading-spinner")
], [
  input([type_("text"), name("name")], []),
  button([type_("submit")], [text("Submit")]),
  div([class("loading-spinner hidden")], [text("Submitting...")])
])

Real-time Updates

div([
  hx.get("/api/status"),
  hx.trigger_polling(hx.Seconds(5), None),
  hx.swap(hx.InnerHTML, None)
], [text("Status: Loading...")])

Documentation

For detailed documentation and examples, visit hexdocs.pm/hx.

Contributing

We welcome contributions! Please see our contributing guidelines for details.

Development

gleam run   # Run the project
gleam test  # Run the tests (32 tests covering all functionality)
gleam shell # Run an Erlang shell

License

This project is licensed under the MIT License - see the LICENSE file for details.

โœจ Search Document