HX
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:
hx.get(url)
- GET requestshx.post(url)
- POST requestshx.put(url)
- PUT requestshx.patch(url)
- PATCH requestshx.delete(url)
- DELETE requests
โก 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.