htmx Tutorial · htmx Basics · Lesson 2 of 30
Get Started with htmx
Add htmx to a page with one script tag (or npm), write a tiny server, and learn how the examples on this site work.
Step 1: add the script
htmx is a single JavaScript file with no dependencies. Put this tag in the <head> of your page:
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/htmx.min.js"
integrity="sha384-2OatzQy1H+Zd/IIrjr1TcuDGqLXeHhbooAyJY1KdQMKnr4LZ22k31GBLdYKHmVjg"
crossorigin="anonymous"></script>
- The version number is pinned (
@2.0.11), so an update can never change your site without you knowing. - The
integrityhash makes the browser refuse the file if a CDN ever serves something different.
For production many teams download the file and serve it themselves (/static/htmx.min.js), so the site doesn't
depend on a CDN. With a bundler you can also install it:
npm install [email protected]
// main.js
import htmx from "htmx.org";
window.htmx = htmx; // optional: makes htmx available to inline scripts and the browser console
Step 2: a complete page
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My first htmx page</title>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/htmx.min.js"
integrity="sha384-2OatzQy1H+Zd/IIrjr1TcuDGqLXeHhbooAyJY1KdQMKnr4LZ22k31GBLdYKHmVjg"
crossorigin="anonymous"></script>
</head>
<body>
<button hx-post="/clicked" hx-swap="outerHTML">Click me</button>
</body>
</html>
Step 3: a server that answers
htmx needs a server that returns HTML. Any language works. Here is the /clicked route in three popular ones:
from flask import Flask, render_template
app = Flask(__name__)
@app.get("/")
def index():
return render_template("index.html") # the page above, saved in templates/index.html
@app.post("/clicked")
def clicked():
return "<p>You clicked the button. This came from Flask!</p>"
import express from "express";
const app = express();
app.use(express.static("public")); // serves public/index.html (the page above)
app.post("/clicked", (req, res) => {
res.send("<p>You clicked the button. This came from Express!</p>");
});
app.listen(3000);
<?php
// clicked.php (use hx-post="/clicked.php" in the page)
echo "<p>You clicked the button. This came from PHP!</p>";
Run the server, open the page, and click: the button is replaced by the paragraph (that's what
hx-swap="outerHTML" means).
How the examples on this site work
Tutorial pages can't run your backend, so every example here includes a pretend server in a <script> at the
bottom. It answers requests right inside the preview:
<button hx-post="/clicked" hx-swap="outerHTML">Click me</button>
<!-- The pretend server: on a real site this part is your backend (Python, PHP, Node, Go...) -->
<script>
server.post("/clicked", () => "<p>You clicked the button. This came from the server!</p>");
</script>
server.get(path, handler)(andserver.post,server.put,server.patch,server.delete) adds a route.- The handler receives
reqwithreq.query(URL parameters),req.body(form values),req.params(parts of the path such as:id) andreq.headers, like Express. - It returns HTML, or
{ status, body, headers }to set a status code or htmx response headers. server.escape(text)escapes user input before putting it into HTML (see Security).- The grey Pretend server log shows every request, its status code and any URL change.
Everything above the script is exactly what you'd write in a real page. Press Edit & try it yourself, change the message or the route, and run it again.
<button hx-get="/time" hx-target="#clock">What time is it on the server?</button>
<p id="clock">Not asked yet.</p>
<!-- The pretend server: on a real site this part is your backend (Python, PHP, Node, Go...) -->
<script>
server.get("/time", () => `The server says it is <strong>${new Date().toLocaleTimeString()}</strong>.`);
</script>
Test yourself
Why do production sites usually pin an exact htmx version, such as [email protected]?
So an update can't change the site's behaviour unexpectedlyBecause unversioned URLs don't workBecause htmx needs a build stepTo make htmx load before the HTML
Show answer
Answer: So an update can't change the site's behaviour unexpectedly
Pinning (and an integrity hash) means you upgrade on purpose, after reading the release notes.