Build Interactive Web Apps with FastHTML (2026)

Web Development Sep 27, 2026

Watch on YouTube

Build Interactive Web Apps with FastHTML (2026)

Build Interactive Web Apps with FastHTML (2026) shows how Answer.AI's FastHTML lets you ship interactive pages in pure Python. You write HTML components as Python functions, wire HTMX attributes as kwargs, and return HTML fragments instead of JSON. No React, no Vite, no separate frontend repo — one file can be a working UI.

If you already use FastAPI + HTMX for no-build UIs or FastAPI app.frontend() to serve an SPA, FastHTML is the next step when you want the HTML itself to be Python. Pair installs with uv + Ruff and you stay in one toolchain from laptop to deploy.

TL;DR

  • FastHTML (python-fasthtml) is Answer.AI's HTMX-first web stack: Python in, HTML out.
  • Install with uv pip install python-fasthtml (or pip); run with serve() / uvicorn.
  • Routes return components like Div, Button, H2 — not templates full of braces.
  • HTMX kwargs (hx_post, hx_target, hx_swap) become real hx-* attributes.
  • With the HX-Request: true header, partial routes return bare HTML fragments for swaps.

Why FastHTML in 2026?

Most Python web tutorials still split the world: FastAPI for JSON APIs, then a JS SPA for the UI. HTMX already flipped that for many teams — the server returns HTML. FastHTML goes further: the HTML tree is native Python, so your editor, types, and tests apply to the UI the same way they apply to the API. For dashboards, admin tools, and internal apps, that is often enough.

StackYou writeBest when
FastAPI + JSON + SPAPython API + JS/TS UIHeavy client state, offline, design systems
FastAPI + Jinja + HTMXPython + HTML templatesYou like templates and partials
FastHTMLPython components + HTMXUI and server logic in one language

Versions tested (2026-09-27)

  • Python 3.14.7 (venv via uv; system also has 3.13.5)
  • python-fasthtml 0.14.13
  • starlette 1.7.0, uvicorn 0.54.0, httpx 0.28.1
  • HTMX loaded by FastHTML from CDN: htmx.org@2.0.7
  • Demo server: http://127.0.0.1:5001
uv venv .venv && source .venv/bin/activate
uv pip install python-fasthtml httpx
# or: pip install python-fasthtml httpx
python main.py
# Link: http://localhost:5001

1. Minimal interactive app

Save this as main.py. The home page renders a counter and two buttons. Clicking Increment POSTs to /inc; the handler returns only the updated <h2 id="count">, and HTMX swaps it into the page.

from fasthtml.common import *

app, rt = fast_app(pico=True)

COUNT = {"n": 0}

@rt("/")
def get():
    return Titled(
        "FastHTML Counter",
        P("A pure-Python interactive page — no React, no build step."),
        Div(
            H2(f"Count: {COUNT['n']}", id="count"),
            Button(
                "Increment",
                hx_post="/inc",
                hx_target="#count",
                hx_swap="outerHTML",
            ),
            Button(
                "Reset",
                hx_post="/reset",
                hx_target="#count",
                hx_swap="outerHTML",
                style="margin-left:0.5rem",
            ),
        ),
        P(A("About this demo", href="/about")),
    )

@rt("/inc")
def post():
    COUNT["n"] += 1
    return H2(f"Count: {COUNT['n']}", id="count")

@rt("/reset")
def post():
    COUNT["n"] = 0
    return H2(f"Count: {COUNT['n']}", id="count")

@rt("/about")
def get():
    return Titled(
        "About",
        P("Built with python-fasthtml — HTML components and HTMX in one Python file."),
        A("← Back", href="/"),
    )

if __name__ == "__main__":
    serve(port=5001)

2. Real HTML from the running app

With the server up, a normal GET returns a full document (title, Pico CSS, HTMX script, and the counter). Notice the hx-post / hx-target / hx-swap attributes on the buttons — FastHTML mapped the Python kwargs 1:1.

curl -s http://127.0.0.1:5001/ | rg 'Count:|hx-post|htmx'
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.7/dist/htmx.js"></script>
<h2 id="count">Count: 0</h2>
<button hx-post="/inc" hx-swap="outerHTML" hx-target="#count">Increment</button>
<button hx-post="/reset" hx-swap="outerHTML" hx-target="#count" ...>Reset</button>

3. HTMX partials — the interactive part

Browsers sending HTMX requests include HX-Request: true. FastHTML then returns the bare fragment your swap expects (not a full HTML page). We reproduced that with httpx:

curl -s -X POST -H 'HX-Request: true' http://127.0.0.1:5001/inc
# <h2 id="count">Count: 1</h2>

curl -s -X POST -H 'HX-Request: true' http://127.0.0.1:5001/inc
# <h2 id="count">Count: 2</h2>

curl -s -X POST -H 'HX-Request: true' http://127.0.0.1:5001/reset
# <h2 id="count">Count: 0</h2>

Same calls via httpx in the demo folder produced identical bodies — status 200 each time. That is the whole interaction model: Python mutates state, returns HTML, HTMX patches the DOM.

4. When to use FastHTML vs FastAPI + HTMX

  • Prefer FastHTML when the UI is the product of Python components, you want one process and one language, and you are happy with server-driven HTML.
  • Prefer FastAPI + HTMX when you already have a FastAPI JSON API, OpenAPI clients, or Jinja templates you do not want to rewrite — see our FastAPI HTMX guide.
  • Prefer an SPA behind FastAPI when you need rich client routing or a separate frontend team — see serving an SPA with app.frontend().

FastHTML still runs on Starlette/uvicorn under the hood, so deployment looks familiar. The difference is the default response type: HTML components, not Pydantic models.

Common pitfalls

  • Expecting JSON. Handlers should return components or HTML strings. If you need a JSON API, expose a separate route or stick with FastAPI for that surface.
  • Forgetting hx_target / hx_swap. Without them HTMX may replace the wrong node (or the triggering element). Match the id you return.
  • Testing partials without HX-Request. A bare curl -X POST /inc may wrap the fragment in a full page. Send the HTMX header (or click in the browser) to see the real swap body.
  • Global mutable state in production. The demo uses a module dict for clarity. Use a database, session, or scoped store for real apps.

What to do next

  1. Run the counter locally with uv pip install python-fasthtml and python main.py.
  2. Add a form (Input, Form, hx_post) that echoes a name into a target div.
  3. Compare the same UI in FastAPI + Jinja if you already have that stack — pick the one your team can ship faster.
  4. Keep installs/lint fast with uv + Ruff.

FastHTML will not replace every SPA — and it does not try to. For interactive Python tools in 2026, returning HTML from Python is often the shortest path from idea to clickable UI.

Share:
Last updated: September 2026
Built-in Functions in Python 2026 str() in Python 2026: String Conversion + Modern Formatting & Best Practices memoryview with JAX in Python 2026: Zero-Copy NumPy → JAX Array Interop + Efficient ML Examples memoryview with TensorFlow in Python 2026: Zero-Copy NumPy → Tensor Interop + GPU Pinning & ML Examples memoryview with TensorFlow in Python 2026: Zero-Copy NumPy → Tensor Interop + ML Examples memoryview with NumPy & PyTorch in Python 2026: Zero-Copy Views, Efficient Slicing & ML Interop Examples memoryview with NumPy in Python 2026: Zero-Copy Views, Efficient Slicing & Real ML Examples memoryview() in Python 2026: Zero-Copy Magic for Large Binary Data + Real Examples import() in Python 2026: How It Works, Security Risks & Modern Alternatives (importlib) zip() in Python 2026: How to Use It, strict=True Behavior & Real-World Examples vars() in Python 2026: Accessing Object Namespace + Modern Introspection Patterns type() in Python 2026: Dynamic Type Inspection & Object Creation + Modern Patterns tuple() in Python 2026: Immutable Sequences + Modern Patterns & Best Practices super() in Python 2026: Method Resolution & Modern Inheritance Patterns sum() in Python 2026: Summing Iterables + Modern Numeric Patterns & Best Practices staticmethod() in Python 2026: Static Methods, Utility Functions & Modern Best Practices sorted() in Python 2026: Sorting Iterables + Modern Patterns & Best Practices slice() in Python 2026: Creating Slice Objects + Modern Patterns & Best Practices setattr() in Python 2026: Dynamic Attribute Setting + Modern Patterns & Safety set() in Python 2026: Mutable Sets Creation + Modern Patterns & Best Practices