Using HTMX in a Rails Application

HTMX is a lightweight JavaScript library that allows you to perform AJAX requests with declarative attributes in your HTML. This guide will help you transition from Rails UJS or Hotwire to using HTMX for dynamic interactions.

Installation

  1. Add HTMX to your project. You can use a CDN for simplicity:

    <script src="https://unpkg.com/htmx.org"></script>
    

    Alternatively, install it via npm or yarn:

    npm install htmx.org
    
  2. Include the HTMX script in your layout file:

    <head>
      <script src="/path-to-htmx.min.js"></script>
    </head>
    

Using HTMX with Rails

Basic Example: Form Submission

  1. Update your form to include HTMX attributes. For example:

    <%= form_with url: posts_path, method: :post, html: { "hx-post": posts_path, "hx-target": "#response-container" } do |form| %>
      <%= form.text_field :title %>
      <%= form.submit "Submit" %>
    <% end %>
    
    • hx-post: Specifies the endpoint for the AJAX request.
    • hx-target: Defines where to insert the server's response.
  2. Add a target container in your HTML where the response will be rendered:

    <div id="response-container"></div>
    

Link Handling

For links, you can use HTMX attributes like hx-get:

<%= link_to "Load More", posts_path, data: { "hx-get": posts_path, "hx-target": "#post-list" } %>
<div id="post-list"></div>

Conditional Responses with HX-Request Header

HTMX sends an HX-Request header in all its requests. Use this to serve different responses for HTMX vs. regular requests.

  1. In your controller action:

    def create
      @post = Post.new(post_params)
      if @post.save
        if request.headers["HX-Request"]
          render partial: "posts/post", locals: { post: @post }
        else
          redirect_to posts_path, notice: "Post created successfully."
        end
      else
        if request.headers["HX-Request"]
          render partial: "shared/errors", locals: { resource: @post }, status: :unprocessable_entity
        else
          render :new
        end
      end
    end
    
  2. Create the _post.html.erb partial for rendering the new post dynamically:

    <div class="post">
      <h3><%= post.title %></h3>
    </div>
    
  3. Optionally handle errors with a _errors.html.erb partial:

    <ul>
      <% resource.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
    </ul>
    

Updating Content with hx-swap

By default, HTMX swaps the content of the target. Customize this behavior using the hx-swap attribute:

<%= form_with url: posts_path, method: :post, html: { "hx-post": posts_path, "hx-target": "#post-list", "hx-swap": "beforeend" } do |form| %>
  <%= form.text_field :title %>
  <%= form.submit "Add Post" %>
<% end %>
<div id="post-list"></div>
  • hx-swap="beforeend" appends the new content to the target.

Sending Data with hx-vals

Use hx-vals to send additional data with the request:

<button hx-post="/some-endpoint" hx-vals='{"extra_param": "value"}'>Submit</button>

Handling Events

HTMX provides events for handling responses:

document.body.addEventListener("htmx:afterRequest", function (event) {
  console.log("Request completed:", event);
});

Debugging HTMX

Enable verbose logging to troubleshoot HTMX:

htmx.config.logging = true;

Summary

HTMX replaces Rails UJS or Hotwire with a simpler, declarative approach. By adding hx- attributes to your HTML and leveraging Rails' respond_to with the HX-Request header, you can build dynamic applications with minimal JavaScript.