The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Getting Started with htmxr

What is htmxr?

htmx is a lightweight JavaScript library (~16kb) that lets any HTML element send HTTP requests — not just <a> and <form> tags.

The core philosophy is HTML over the wire: your server returns HTML fragments, not JSON. The browser swaps those fragments directly into the page without a full reload.

htmxr is the R wrapper: it provides htmltools-based primitives to generate htmx attributes and build complete pages, backed by a plumber2 server.


Installation

install.packages("htmxr")

# development version
pak::pak("hyperverse-r/htmxr")

htmxr uses plumber2 as its HTTP server — make sure it is installed alongside htmxr.


How htmx works

Every htmx interaction follows the same four-step cycle:

  1. User triggers an event — a click, an input change, a page load, a form submission…
  2. htmx sends an HTTP request to your server (GET or POST)
  3. Your server returns an HTML fragment — a snippet of HTML, not JSON
  4. htmx swaps the fragment into the targeted DOM element

You control this cycle through five core attributes:

Attribute htmxr parameter What it does
hx-get get = "/url" Send GET request on trigger
hx-post post = "/url" Send POST request on trigger
hx-target target = "#id" CSS selector of the element to update
hx-swap swap = "innerHTML" How to insert the response (innerHTML, outerHTML…)
hx-trigger trigger = "click" What triggers the request (click, change, load…)

In htmxr, these map directly to function parameters — no JavaScript to write.


Your first htmxr app

The fastest way to see htmxr in action is to run the built-in hello example:

library(htmxr)
hx_run_example("hello")

This launches an Old Faithful histogram where a slider controls the number of bins. Let’s walk through how it works.

The page

The page is served by a GET / route. hx_page() wraps the full HTML document and injects the htmx script automatically. hx_head() handles the <head> tag.

The slider is built with hx_slider_input(). Three htmx parameters connect it to the server:

hx_slider_input(
  id = "bins",
  label = "Number of bins:",
  value = 30,
  min = 1,
  max = 50,
  get = "/plot",                         # send GET /plot on trigger
  trigger = "input changed delay:300ms", # trigger: input event, debounced 300ms
  target = "#plot"                       # replace the content of #plot
)

The plot container is a plain <div> with an id. hx_set() adds htmx attributes to it so the plot loads immediately on page load:

tags$div(id = "plot") |>
  hx_set(
    get = "/plot",
    trigger = "load",       # fires once when the element is loaded
    target = "#plot",
    swap = "innerHTML"
  )

The fragment endpoint

The /plot route returns an SVG string — an HTML fragment, not a full page:

#* @get /plot
#* @query bins:integer(30)
#* @parser none
#* @serializer none
function(query) {
  generate_plot(query$bins)
}

When the slider moves, htmxr sends GET /plot?bins=35. The server returns the SVG. htmx swaps it into #plot. No JavaScript, no JSON parsing, no manual DOM manipulation.

The htmx connection

slider input event
       │
       ▼
GET /plot?bins=35   ──►   server renders SVG
                                   │
                    ◄──────────────┘
          htmx swaps SVG into #plot

Anatomy of an htmxr project

A minimal htmxr app needs only two things:

api.R — your plumber2 API with two kinds of routes:

hx_serve_assets() — registers the htmx JavaScript file as a static asset on your plumber2 router. hx_page() and hx_head() handle injecting the <script> tag automatically.

# Minimal api.R structure
library(htmxr)

#* @get /
#* @serializer html
function() {
  hx_page(
    hx_head(
      title = "My app"
    ),
    tags$div(
      id = "content"
    ) |>
      hx_set(
        get = "/content",
        trigger = "load",
        target = "#content"
      )
  )
}

#* @get /content
#* @serializer html
function() {
  tags$p("Hello from the server!")
}

Launch API with:

library(htmxr)

pr <- plumber2::api("api.R") |>
  hx_serve_assets()

cat("\n🚀 Launch API...\n")
cat("🌐 Webapp: http://127.0.0.1:8080/\n\n")

pr$ignite(port = 8080)

Next steps

Explore more built-in examples:

# List all available examples
hx_run_example()

# Dynamic table filtering with hx_select_input()
hx_run_example("select-input")

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.