htmxr does not replace Shiny — it answers different questions.
Before explaining what htmxr is, it is worth appreciating what Shiny achieved.
Shiny democratized web application development for R users. It handles the browser-to-R communication invisibly, so data scientists can focus entirely on their analysis rather than web infrastructure. What Shiny does remarkably well:
shinydashboard, bslib, DT, plotly…) built specifically for ShinyShiny is the right tool for rich analytical dashboards, scientific interfaces, and internal data exploration tools.
Shiny and htmxr are built on fundamentally different communication models:
| Shiny | htmxr | |
|---|---|---|
| Communication | WebSocket (persistent connection) | HTTP (request/response) |
| Paradigm | Reactive graph | Explicit HTTP requests |
| UI updates | Shiny decides what to reload | You target the DOM precisely |
| State | R session per user | Database or URL |
| Backend | R only | Any HTTP server |
The practical difference: in Shiny, your server reacts. In htmxr, your browser asks.
Shiny maintains one R session per connected user. That session holds all the reactive state — inputs, outputs, intermediate computations — and Shiny decides when to re-evaluate which outputs. This model is powerful and ergonomic, but it means each user consumes a persistent R process.
htmxr apps are largely stateless. Each HTTP request is independent. The server computes something, returns an HTML fragment, and forgets. State lives in the database, the URL, or the client — not in a long-lived R session.
Shiny reloads entire output blocks (uiOutput, renderUI) — sometimes more than strictly necessary. htmx updates exactly the targeted element, nothing more. The result is smoother interfaces, less visual flicker, and a faster perceived experience.
Try the infinite scroll example to see this in practice — 53,940 diamonds loaded progressively, with only the next batch of cards appended to the DOM on each scroll event:
hx_run_example("infinity-scroll")Shiny’s persistent session model means memory consumption scales linearly with the number of connected users. A pool of shared plumber2 workers handles far more concurrent users on the same hardware.
Deployment is standard: nginx, Docker, any VPS, any cloud platform. No Shiny Server license required.
htmxr responses are plain HTML. This means:
An htmxr app can work even when JavaScript is disabled — links and forms remain functional. You can enrich a static HTML page progressively, or add dynamic behaviour to an existing app without rewriting it from scratch.
htmxr generates standard HTML backed by a standard HTTP API. The same plumber2 endpoints that serve your htmxr frontend can be consumed by mobile apps, other services, or scripts in any language — without translation or adaptation.
This is particularly valuable when a proof-of-concept built in R needs to grow into a production system: the business logic stays in R, while other parts of the stack can call the same endpoints.
Choose htmxr when:
Choose Shiny when:
DT, plotly, bslib…)htmxr is not a Shiny replacement. It is a different tool for different problems. If you need reactive, session-based dashboards in R, Shiny is the right choice. If you need scalable, HTTP-native apps that interoperate beyond R, htmxr is the answer.