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.

Create a free account to save this page, take notes and track your progress. Sign in

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 integrity hash 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>"

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:

Click to replace the buttonTry it yourself
<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) (and server.post, server.put, server.patch, server.delete) adds a route.
  • The handler receives req with req.query (URL parameters), req.body (form values), req.params (parts of the path such as :id) and req.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.

Ask the server for the timeTry it yourself
<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]?

  1. So an update can't change the site's behaviour unexpectedly
  2. Because unversioned URLs don't work
  3. Because htmx needs a build step
  4. To 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.

Was this page helpful?