htmx Tutorial: Get Started with htmx (Step-by-Step)

Okay, here’s a comprehensive article covering an htmx tutorial, aiming for approximately 5000 words:

htmx Tutorial: Get Started with htmx (Step-by-Step)

Introduction: Embracing Simplicity with htmx

In the ever-evolving landscape of web development, frameworks and libraries often dominate the conversation. React, Angular, and Vue.js have become household names, each offering powerful tools for building complex, dynamic user interfaces. However, this complexity often comes at a cost: large bundle sizes, steep learning curves, and a reliance on JavaScript for even the simplest interactions. Enter htmx, a library that takes a radically different approach, embracing the principles of Hypermedia as the Engine of Application State (HATEOAS) and extending HTML’s capabilities to create dynamic web applications with minimal JavaScript.

htmx empowers you to build modern web interfaces using HTML attributes. Instead of writing intricate JavaScript to handle AJAX requests, DOM manipulation, and event handling, you can achieve the same results directly within your HTML markup. This approach leads to:

  • Reduced Complexity: Less JavaScript means a smaller codebase, easier debugging, and improved maintainability.
  • Improved Performance: Smaller payloads translate to faster page loads and a smoother user experience.
  • Enhanced Accessibility: htmx leverages the inherent semantics of HTML, making your applications more accessible by default.
  • Progressive Enhancement: Your applications work even without JavaScript, providing a solid baseline experience for all users.
  • Simplified Development: Focus on your backend logic and let htmx handle the front-end interactions.

This tutorial will guide you through the fundamentals of htmx, providing step-by-step instructions, practical examples, and explanations to get you started building dynamic web applications with this powerful library. We’ll cover everything from basic setup to advanced features, ensuring you have a solid understanding of htmx’s capabilities and how to leverage them effectively.

Part 1: Setting Up Your Development Environment

Before we dive into htmx itself, we need to set up a basic development environment. You’ll need:

  1. A Text Editor or IDE: Choose your favorite text editor (like VS Code, Sublime Text, Atom) or a full-fledged Integrated Development Environment (IDE) like WebStorm.
  2. A Web Server: htmx relies on server-side responses, so you’ll need a web server to serve your HTML files and handle requests. Here are a few options:
    • Python’s built-in http.server (Recommended for Simplicity): This is the easiest way to get started. Open your terminal or command prompt, navigate to your project directory, and run:
      bash
      python -m http.server

      This will start a simple web server on port 8000. You can access your project in your browser at http://localhost:8000.
    • Node.js with http-server: If you have Node.js and npm installed, you can use the http-server package:
      bash
      npm install -g http-server
      http-server .

      This will also start a server on port 8000.
    • Live Server (VS Code Extension): If you’re using VS Code, the Live Server extension provides a convenient way to serve your files and automatically reload the browser on changes.
    • Apache, Nginx (For More Complex Setups): For more advanced deployments, you might use a full-fledged web server like Apache or Nginx.
  3. (Optional) A Backend Language/Framework: While htmx can work with any backend that returns HTML, we’ll use simple examples with just static HTML files first. Later, we will introduce a Python Flask backend to showcase dynamic content generation.

Step 1: Create a Project Directory

Create a new directory for your project. For example:

bash
mkdir htmx-tutorial
cd htmx-tutorial

Step 2: Create an index.html File

Inside your project directory, create a file named index.html. This will be our main HTML file.

“`html






htmx Tutorial

Welcome to htmx!

This is a basic HTML page.


“`

Step 3: Include htmx

There are two primary ways to include htmx in your project:

  • Via CDN (Recommended for Beginners): The easiest way is to include htmx from a Content Delivery Network (CDN). Add the following <script> tag within the <head> of your index.html file:

    html
    <script src="https://unpkg.com/[email protected]" integrity="sha384-FhXw7b6AlE/jyjlZH5iHa/tTe9EpJ1Y55RjcgPbjeWMskSxZt1v9qkxLJWNJaGni" crossorigin="anonymous"></script>

    (Note: replace 1.9.6 with the latest version number from the official htmx website: https://htmx.org/)
    * Download and Host Locally: You can download the htmx library from the official website and host it yourself. This gives you more control but requires more setup. Download htmx.min.js and place it in your project directory (e.g., in a js folder). Then, include it in your HTML:

    html
    <script src="js/htmx.min.js"></script>

Your index.html file should now look like this (using the CDN method):

“`html






htmx Tutorial


Welcome to htmx!

This is a basic HTML page.


“`

Step 4: Start Your Web Server

Using your chosen method (Python’s http.server, http-server, Live Server, etc.), start your web server. Open your web browser and navigate to http://localhost:8000 (or the appropriate address if you’re using a different port or server). You should see your basic HTML page.

Part 2: Your First htmx Interaction: A Simple GET Request

Now that we have our environment set up, let’s create our first htmx interaction. We’ll create a button that, when clicked, fetches content from another HTML file and replaces a portion of the page.

Step 1: Create content.html

Create a new file named content.html in your project directory. This file will contain the content we want to load dynamically.

“`html

This content was loaded with htmx!

“`

Step 2: Modify index.html

Modify your index.html file to include a button and a target element:

“`html






htmx Tutorial


Welcome to htmx!

This is a basic HTML page.

Original content.


“`

Let’s break down the new attributes we added to the button:

  • hx-get="/content.html": This is the core of htmx. It tells htmx to make a GET request to the /content.html URL when the button is clicked. Notice that we’re using a relative URL. This will work because our web server is serving files from the project directory.
  • hx-target="#content-area": This attribute specifies the target element on the page that will be updated with the response from the server. #content-area is a CSS selector that targets the <div> with the ID “content-area”.
  • <div id="content-area">: This is the target element. The content within this <div> will be replaced by the content fetched from content.html.

Step 3: Test It Out

Refresh your browser (http://localhost:8000). Click the “Load Content” button. You should see the “Original content.” text in the #content-area div replaced with “This content was loaded with htmx!”.

Congratulations! You’ve just created your first dynamic interaction with htmx, all without writing a single line of JavaScript.

Part 3: Understanding htmx Attributes and Concepts

Let’s delve deeper into the key htmx attributes and concepts we’ve encountered so far, and explore some additional ones.

Core Attributes:

  • hx-get: Initiates a GET request to the specified URL.
  • hx-post: Initiates a POST request to the specified URL.
  • hx-put: Initiates a PUT request.
  • hx-patch: Initiates a PATCH request.
  • hx-delete: Initiates a DELETE request.
  • hx-target: Specifies the target element to be updated with the server’s response. Can be any valid CSS selector.
  • hx-swap: Controls how the server’s response is swapped into the target element. The default value is innerHTML, but there are many other options:
    • innerHTML: Replaces the inner HTML of the target element.
    • outerHTML: Replaces the entire target element.
    • beforebegin: Inserts the response before the target element.
    • afterbegin: Inserts the response as the first child of the target element.
    • beforeend: Inserts the response as the last child of the target element.
    • afterend: Inserts the response after the target element.
    • none: Doesn’t swap any content (useful for triggering events without updating the DOM).
  • hx-trigger: Specifies the event that triggers the request. The default is click for buttons and submit for forms. Other common values include:
    • keyup: Triggered when a key is released.
    • keydown: Triggered when a key is pressed.
    • change: Triggered when the value of an input element changes.
    • mouseenter: Triggered when the mouse pointer enters an element.
    • mouseleave: Triggered when the mouse pointer leaves an element.
    • load: Triggered when the element is loaded.
    • revealed: Triggered when an element is scrolled into view (requires the htmx-ext="morph" extension).
    • intersect: Triggered when an element intersects with the viewport (requires the Intersection Observer API).
    • You can also combine triggers and use modifiers. For example: keyup[ctrlKey&&shiftKey] or click once or mouseover delay:500ms
  • hx-vals: Allows you to include additional data in the request. This can be a JSON object or a CSS selector to extract values from other elements on the page.
  • hx-headers: Allows you to set custom HTTP headers for the request.
  • hx-indicator: Specifies an element to show as a loading indicator while the request is in progress.
  • hx-confirm: Displays a confirmation dialog before issuing a request.
  • hx-push-url: Pushes the fetched URL into the browser’s history, enabling back/forward navigation.
  • hx-select: Allows you to select a specific portion of the server’s response to be swapped into the target. Uses a CSS selector.
  • hx-params: Controls which parameters are submitted with a request. By default, all parameters from the triggered element and its parents are included.
    • *: Include all parameters (default).
    • none: Include no parameters.
    • not <param-name>: Exclude a specific parameter.
    • <param-name>: Include only a specific parameter.
  • hx-include: Includes the closest ancestor element matching a CSS selector in the request. Useful to send the content of another element along.

Example: hx-swap, hx-trigger, and hx-vals

Let’s modify our example to demonstrate hx-swap, hx-trigger, and hx-vals.

First, add a new file named form.html:
“`html

New Form HTML




“`

Add also thankyou.html:
“`html

Thank you, ! We received your email at .

Modify index.htmlhtml






htmx Tutorial


Welcome to htmx!

This is a basic HTML page.

Original content.



“`

Key changes:

  • hx-get="/form.html" (in index.html): Loads form.html into #content-area
  • hx-swap="outerHTML" (in form.html): This time, we use outerHTML, which means the entire button itself will be replaced by the content of /thankyou.html, not just the content of the button.
  • hx-vals='{"name": "#name", "email":"#email"}' (in form.html): This is crucial. It constructs a JSON object to be sent as data with the GET request. #name and #email are CSS selectors that refer to the input fields within the form. htmx will extract the values of these input fields and include them in the request. Note: this sends the parameters in the URL. If you use hx-post, parameters will be sent in the request body.
  • htmx Events and Inline JavaScript: We use htmx:beforeRequest and htmx:afterRequest events to show and hide a simple loading indicator. We’re also accessing values sent from the form in the JavaScript to update the thankyou.html content. This demonstrates how you can still use JavaScript for targeted enhancements, without relying on it for core functionality.
  • Thankyou Name and Email: After the form submit, the content in thankyou.html is loaded. We use JavaScript to get values from the input fields and dynamically update thankyou.html with the name and email provided.

Run this example. Clicking the “Load Form” button loads the form into the page. Fill out the form and click “Submit Form.” The button, and all the form, is replaced by thank you message, and the message includes the values you put in the form fields.

Part 4: Working with Forms and POST Requests

htmx seamlessly integrates with HTML forms. You can use hx-post to submit form data to the server. Let’s create a more complete form example, and this time, we’ll introduce a simple Python Flask backend to handle the form submission.

Step 1: Install Flask

If you don’t have Flask installed, install it using pip:

bash
pip install Flask

Step 2: Create app.py

Create a file named app.py in your project directory:

“`python
from flask import Flask, request, render_template_string

app = Flask(name)

@app.route(‘/’)
def index():
return render_template_string(open(‘index.html’).read())

@app.route(‘/form.html’)
def form():
return render_template_string(open(‘form.html’).read())

@app.route(‘/content.html’)
def content():
return render_template_string(open(‘content.html’).read())

@app.route(‘/thankyou.html’, methods=[‘GET’, ‘POST’])
def thankyou():
if request.method == ‘POST’:
name = request.form[‘name’]
email = request.form[’email’]
return f”

Thank you, {name}! We received your email at {email}.

” # Simplified response
else:
# Handle GET request (if needed, for direct access)
return render_template_string(open(‘thankyou.html’).read())

if name == ‘main‘:
app.run(debug=True) # Use debug=True for development
“`

Step 3: Update form.html to use POST

“`html

New Form HTML




“`
Key changes:

  • hx-post="/thankyou.html": We now use hx-post to submit the form data using a POST request.
  • hx-target="#content-area": We use hx-target to define where to load the result.
  • <form> tag: The entire form is now enclosed in a <form> tag. This is standard HTML practice, and htmx leverages this structure. The hx-post attribute is placed on the <form> tag itself.
  • name attributes: The input fields have name attributes (e.g., name="name", name="email"). These names are used as keys in the form data sent to the server.
  • type="submit" htmx listens for the submit event, so we need to make the button of submit type.

Step 4: Remove the JavaScript from index.html
Since we now manage the name and email server side, remove the following from the index.html:
html
if (evt.detail.xhr.responseURL.includes("thankyou.html")) {
const name = document.getElementById("name").value;
const email = document.getElementById("email").value;
document.getElementById("thankyou-name").textContent = name;
document.getElementById("thankyou-email").textContent = email;
}

Step 5: Stop the previous server and start the Flask app

Stop any previous server you were running (e.g., python -m http.server). Now, start the Flask application:

bash
python app.py

Flask will run on http://127.0.0.1:5000 by default. Open this address in your browser.

Step 6: Test the Form

Click “Load Form,” fill in the form fields, and click “Submit Form.” The Flask backend will receive the POST request, extract the name and email values from the form data, and return a simple HTML response containing a thank you message. htmx will then replace the content of #content-area with this response.

This example demonstrates a more realistic scenario where you’re handling form submissions on the server. The Flask backend is minimal, but it illustrates the basic principle: htmx sends the form data, your backend processes it, and returns HTML, which htmx then displays.

Part 5: Advanced htmx Features

Let’s explore some more advanced htmx features that can enhance your applications.

1. hx-select and hx-swap (Advanced)

We’ve already touched on hx-swap, but let’s combine it with hx-select for more fine-grained control.

Create a new file partial.html:

“`html

This is a header

This is the paragraph we want.

This is a span.

Update `index.html`html






htmx Tutorial


Welcome to htmx!

This is a basic HTML page.


Original content.



“`

Update app.py
“`python
from flask import Flask, request, render_template_string

app = Flask(name)

@app.route(‘/’)
def index():
return render_template_string(open(‘index.html’).read())

@app.route(‘/form.html’)
def form():
return render_template_string(open(‘form.html’).read())

@app.route(‘/content.html’)
def content():
return render_template_string(open(‘content.html’).read())

@app.route(‘/partial.html’)
def partial():
return render_template_string(open(‘partial.html’).read())

@app.route(‘/thankyou.html’, methods=[‘GET’, ‘POST’])
def thankyou():
if request.method == ‘POST’:
name = request.form[‘name’]
email = request.form[’email’]
return f”

Thank you, {name}! We received your email at {email}.

” # Simplified response
else:
# Handle GET request (if needed, for direct access)
return render_template_string(open(‘thankyou.html’).read())

if name == ‘main‘:
app.run(debug=True) # Use debug=True for development
“`

  • hx-select="#paragraph": This tells htmx to only select the element with the ID “paragraph” from the response of /partial.html.
  • hx-swap="outerHTML": The selected element (#paragraph) will replace the entire #content-area div.

Clicking “Load Partial” will now only load and display the paragraph from partial.html, replacing the #content-area div.

2. hx-push-url

hx-push-url allows you to update the browser’s URL and history without a full page reload. This is essential for creating a SPA-like experience.

Add a new button to index.html.

html
<button hx-get="/content.html" hx-target="#content-area" hx-push-url="true">
Load Content (with URL push)
</button>

Now, when you click this button, the content will be loaded, and the browser’s URL will change to /content.html. You can use the browser’s back and forward buttons to navigate between the states. You can also set hx-push-url to a specific URL.

3. hx-confirm

Add a confirmation dialog before a potentially destructive action.

html
<button hx-delete="/resource/123" hx-target="#content-area" hx-confirm="Are you sure you want to delete this?">
Delete Resource
</button>

This will display a confirmation dialog before sending the DELETE request. (You’d need to implement the /resource/123 endpoint in your backend, of course.)

4. hx-indicator

We already showed a basic hx-indicator implementation with JavaScript events. Let’s use the hx-indicator attribute for a cleaner way:
Update index.html

“`html






htmx Tutorial


Welcome to htmx!

This is a basic HTML page.



Original content.


``
Add to the
of yourindex.html`:

“`html

“`

  • hx-indicator="#indicator": This attribute is added to the buttons. It tells htmx to use the element with the ID “indicator” as the loading indicator.
  • class="htmx-indicator": We add the class to the loading indicator.
  • CSS: We define CSS rules to control the visibility of the indicator. htmx-request is a class that htmx automatically adds to the element triggering the request while it’s in progress.

Now, the “Loading…” message will automatically appear and disappear during the request, without needing custom JavaScript event listeners. htmx handles the timing for you.

5. htmx Extensions

htmx has a powerful extension system that allows you to add new functionality. You include extensions using the hx-ext attribute. Let’s look at a couple of useful extensions:

  • client-side-templates: This extension allows you to use client-side templating engines (like Mustache, Handlebars, Nunjucks) to render data received from the server. This is useful for more complex data rendering without having to return fully formed HTML from the server.

  • morph: Enables smooth DOM morphing transitions when content changes. This makes updates feel much more polished than abrupt replacements.

To use an extension, you need to include the extension’s JavaScript file (usually available via CDN) after the main htmx script. Then, you use the hx-ext attribute on the element where you want to use the extension.

Example (using the morph extension for smoother transitions):
Update index.html

“`html






htmx Tutorial



Welcome to htmx!

This is a basic HTML page.



Original content.


``
* **
**: Includes themorphextension *after* the main htmx script.
* **
hx-ext=”morph”:** This tells htmx to use themorph` extension on these buttons.

Now, when you click the buttons, the content transitions will be smooth and animated, instead of abruptly changing.

Part 6: Best Practices and Considerations

  • Progressive Enhancement: Always design your application with progressive enhancement in mind. Ensure that your core functionality works even without JavaScript enabled. htmx makes this easier, but you should still structure your HTML semantically and provide fallback mechanisms where necessary.

  • Server-Side Rendering: htmx works best with server-side rendered HTML. Your backend should be responsible for generating the initial HTML and handling subsequent requests.

  • Security: Be mindful of security considerations, especially when handling user input. Sanitize and validate data on the server-side to prevent cross-site scripting (XSS) and other vulnerabilities.

  • Error Handling: Implement proper error handling on both the client-side and server-side. You can use htmx events (like htmx:responseError) to handle errors gracefully.

  • Choosing between GET and POST: Use hx-get for retrieving data and hx-post (or other appropriate methods like hx-put, hx-patch, hx-delete) for submitting data or performing actions that modify state on the server.

*

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top