from fasthtml.common import *
from monsterui.all import *
= fast_app(hdrs=Theme.blue.headers())
app, rt
@rt
def index():
return Card(
"Hello World"),
H1("Ex Button", cls=ButtonT.primary))
Button(
serve()
HTMX Foundations for FastHTML
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:
- What action to take: Get new content or send information (
hx-get
/hx-post
). - When to take it: For example, when a button is clicked (
hx-trigger
). - Where to put the result: Which part of the page should be updated (
hx-target
). - 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:
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(
"Name", id='name'),
LabelInput("Submit", cls=ButtonT.primary),
Button(=add_name)
hx_get
@rt
def add_name(name:str):
= H1(f"{name}!!!")
result return result
This is a simple form with a name field and a submit button. When you click the button, FastHTML:
- Sends the name you typed to the
add_name
function - Runs the
add_name
function - Creates a H1 element with the name you typed
- Returns the H1 element to FastHTML
- Replaces the original form on the page with the H1 element
💡
hx_get
is for getting information, but you can also usehx_post
for sending information andhx_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("Name", id='name'),
LabelInput("Submit", cls=ButtonT.primary),
Button(=add_name2,
hx_get='#result'
hx_target
),id='result')
Div(
)
@rt
def add_name2(name:str):
= H1(f"Hello {name}!!!")
result return result
Our page has:
- A form where you type your name at the top
- An empty box below it (the
Div
withid='result'
)
Without hx_target
, clicking submit would replace the entire form with your greeting. But with hx_target='#result'
:
- You type your name and click submit
- The form stays where it is (so you can use it again)
- Your greeting appears in the empty box below
- 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("Name", id='name'),
LabelInput("Submit", cls=ButtonT.primary),
Button(=add_name3,
hx_get='#result',
hx_target='beforeend'
hx_swap
),id='result')
Div(
)
@rt
def add_name3(name:str):
= H1(f"Hello {name}!!!")
result 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',
=add_name4,
hx_get='#result',
hx_target='input changed'
hx_trigger
)
),id='result')
Div(
)
@rt
def add_name4(name:str):
= H1(f"Hello {name}!!!")
result 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 eventschanged
: 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:
- Simplicity: Leverages HTMX to drastically reduce frontend JavaScript complexity.
- Python-Centric: Allows developers to stay primarily within the Python ecosystem they know and love.
- Performance: Built on the fast Starlette ASGI framework.
- 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 *
= fast_app()
app, rt
@rt
def index():
return Titled(
"Hello FastHTML",
"Simpler web development begins here.")
Subtitle(
)
serve()
Run it with python main.py
and visit localhost:5001
in your browser.