HTMX Foundations for FastHTML

HTMX Foundations for FastHTML

Python
Web Development
FastHTML
HTMX
Learn how FastHTML makes web development simpler by combining HTMX’s power with Python’s ease of use.
Author

Isaac Flath

Published

April 21, 2025

Building Simple Web Apps Without the Overhead

Framework fatigue is real. 200 lines of code for a simple interactive form? There’s a better way.

When building annotation tools, internal dashboards, or quick MVPs, you need to move fast and iterate quickly. Modern JavaScript frameworks often add complexity - build steps, state management, and complex tooling can slow you down when you just need to get something working.

FastHTML + HTMX offers a simpler path. You can build interactive web apps using Python and HTML, perfect for rapid prototyping and internal tools where simplicity and speed matter more than cutting-edge design features.

This post will guide you through the foundations of HTMX in FastHTML, with a focus on the HTMX side of things.

Why HTMX?

HTMX offers a much simpler approach. It asks: “What if HTML itself could handle common interactive tasks?” Instead of writing separate scripts, HTMX lets you add special instructions (called attributes) directly to your HTML tags.

HTMX uses four main instructions (hx-get/post, hx-trigger, hx-target, and hx-swap) that tell the browser:

  1. What action to take: Get new content or send information (hx-get/hx-post).
  2. When to take it: For example, when a button is clicked (hx-trigger).
  3. Where to put the result: Which part of the page should be updated (hx-target).
  4. How to update it: Should the new content replace the old, or be added alongside it (hx-swap).

You define these instructions right in your HTML. This approach keeps most of the logic on the server, which can feel simpler and more organized for many developers.

Why FastHTML?

HTMX handles telling the browser how to fetch and display updates. But you still need a server to provide the actual HTML content for those updates. This is where FastHTML comes in, especially for Python developers.

FastHTML is built specifically to work smoothly with HTMX. It lets you write simple Python functions that create the small pieces of HTML needed (with the HTMX instructions) for updates. FastHTML then automatically connects these Python functions to the HTMX instructions in your webpage. The goal is to feel like you’re just writing Python code

🐍 This post assumes some basic familiarity with Python and HTML. Understanding the core ideas of HTMX is helpful but not strictly required, as we’ll touch upon the relevant concepts.

Setting Up FastHTML

Let’s start by setting up a basic FastHTML application:

from fasthtml.common import *
from monsterui.all import *

app, rt = fast_app(hdrs=Theme.blue.headers())

@rt
def index():
    return Card(
        H1("Hello World"), 
        Button("Ex Button", cls=ButtonT.primary))

serve()

This example shows the core of FastHTML’s approach. Notice how we create route handlers using the rt decorator, and that route returns a Python function that generates HTML.

The Card component creates a styled container, while components like H1 and Button create the corresponding HTML elements. The cls attribute sets the CSS class.

Exploring HTMX Through FastHTML

Now let’s explore each of the four main HTMX attributes and see how FastHTML implements them. These are the building blocks that allow you to create interactive websites.

❗️❗️ The HTMX philosophy is simple: “Call a function (hx-get) when I want (hx-trigger) and put the results where I want (hx-target) however I want (hx-swap).”

Think of these attributes as answering four basic questions:

  • hx-get/post: “What should I fetch from the server?”
  • hx-trigger: “When should I fetch it?”
  • hx-target: “Where should I put the result?”
  • hx-swap: “How should I display the result?”

Let’s see how these work in practice.

1. Making Requests with hx-get

The hx-get attribute tells an element “when activated, go run this function and get the result from the server.” In FastHTML, we can connect our Python functions directly to this attribute.

Example: A Simple Form with hx-get

@rt
def basic_form():
    return Form(
        LabelInput("Name", id='name'),
        Button("Submit", cls=ButtonT.primary),
        hx_get=add_name)

@rt
def add_name(name:str):
    result = H1(f"{name}!!!")
    return result

This is a simple form with a name field and a submit button. When you click the button, FastHTML:

  1. Sends the name you typed to the add_name function
  2. Runs the add_name function
  3. Creates a H1 element with the name you typed
  4. Returns the H1 element to FastHTML
  5. Replaces the original form on the page with the H1 element

💡 hx_get is for getting information, but you can also use hx_post for sending information and hx_delete for removing things.

2. Targeting Elements with hx-target

The hx-target attribute tells the browser “put the results here, not where I clicked.” Without it, results usually replace the element that was clicked.

Example: Targeting a Specific Element

@rt
def basic_form2():
    return Main(
        Form(
            LabelInput("Name", id='name'),
            Button("Submit", cls=ButtonT.primary),
            hx_get=add_name2,
            hx_target='#result'
        ),
        Div(id='result')
    )

@rt
def add_name2(name:str):
    result = H1(f"Hello {name}!!!")
    return result

Our page has:

  1. A form where you type your name at the top
  2. An empty box below it (the Div with id='result')

Without hx_target, clicking submit would replace the entire form with your greeting. But with hx_target='#result':

  1. You type your name and click submit
  2. The form stays where it is (so you can use it again)
  3. Your greeting appears in the empty box below
  4. The #result means “find the element with ID ‘result’”

3. Controlling Insertion with hx-swap

The hx-swap attribute answers “how should I add this new content?” By default, it erases what was there and puts in the new content, but you can change this.

Example: Appending Content with hx-swap

@rt
def basic_form3():
    return Main(
        Form(
            LabelInput("Name", id='name'),
            Button("Submit", cls=ButtonT.primary),
            hx_get=add_name3,
            hx_target='#result',
            hx_swap='beforeend'
        ),
        Div(id='result')
    )

@rt
def add_name3(name:str):
    result = H1(f"Hello {name}!!!")
    return result

This example shows how to build a list of items: 1. You type “Bob” and click submit - “Hello Bob!!!” appears 2. You type “Alice” and click again 3. Instead of replacing “Hello Bob!!!”, it adds “Hello Alice!!!” below it 4. You end up with both greetings, one after another

Think of hx_swap options like different ways to update a grocery list: - innerHTML (default): Erase the list and write a new one - beforeend: Add items to the bottom of the list - afterbegin: Add items to the top of the list - outerHTML: Replace the entire notepad, not just the list

The beforeend option is perfect for chat interfaces or activity feeds where you want to keep adding new items.

4. Controlling “When” with hx-trigger

The hx-trigger attribute answers “when should this happen?” Normally, buttons wait for clicks and forms wait for submission, but you can change this.

Example: Trigger on Input Changes

@rt
def basic_form4():
    return Main(
        Form(
            LabelInput(
                "Name", 
                id='name',
                hx_get=add_name4,
                hx_target='#result',
                hx_trigger='input changed'
            )
        ),
        Div(id='result')
    )

@rt
def add_name4(name:str):
    result = H1(f"Hello {name}!!!")
    return result

Instead of responding when you click a button, now the input responds as you type: 1. Start typing “B” - immediately see “Hello B!!!” 2. Continue to “Bo” - instantly updates to “Hello Bo!!!” 3. Finish with “Bob” - shows “Hello Bob!!!” 4. No need to click any buttons or submit forms!

The input changed means:

  • input: Respond to typing events
  • changed: Only when the content actually changes (not for arrow keys)

Think of it like having different kinds of doorbells:

  • Regular doorbell: You have to press it (like a button click)
  • Motion sensor: Activates automatically when someone approaches (like hx_trigger='input')

This is how Google Search gives you suggestions as you type, or how some forms validate your input in real-time.

By combining these four HTMX attributes in different ways, you can create all kinds of interactive features without leaving Python. FastHTML makes this even more straightforward by handling all the connections between your Python functions and the web page.

💡 There’s a lot more to HTMX and FastHTML, but this should give you a good foundation to start. Check out the FastHTML Gallery and HTMX documentation for more examples and advanced usage of HTMX

Why FastHTML Clicks

FastHTML resonates because it combines several powerful ideas:

  1. Simplicity: Leverages HTMX to drastically reduce frontend JavaScript complexity.
  2. Python-Centric: Allows developers to stay primarily within the Python ecosystem they know and love.
  3. Performance: Built on the fast Starlette ASGI framework.
  4. Developer Experience: Offers features like type hints and intuitive component composition.

It brings back a simpler model of web development, where the server renders HTML, but with the modern twist of targeted AJAX updates powered by HTMX, all orchestrated cleanly with Python.

Getting Started

Ready to dive in? Here’s how to get started with FastHTML:

Install FastHTML and MonsterUI:

pip install python-fasthtml monsterui

Create a new FastHTML app in main.py:

# Create app.py
from fasthtml.common import *
from monsterui.all import *

app, rt = fast_app()

@rt
def index():
    return Titled(
        "Hello FastHTML",
        Subtitle("Simpler web development begins here.")
    )

serve()

Run it with python main.py and visit localhost:5001 in your browser.