It's what happens when there's not enough time during the day...

Apr 19, 2023 - Apr 27, 2023
Salt Palace Convention Center

A Python-backed Frontend: Featuring HTMX and Tailwind

Presented: Apr 19, 2023



Want to bring hypermedia into your web design workflow, ditching the complexity of JSON-over-HTTP for a more RESTful approach?

Create and design your web application with htmx and spark joy in your design process. Splash in a little Tailwind CSS, too. (Ssshh. You're a Full-Stack Developer now.)


These notes are for a tutorial presented at PyCon US 2023. In case you missed something during the tutorial (or happened upon this page out of interest in the subject), you may be able to find additional information here, or you can reach out to me individually, if something is missing.


Music Binder

This repository was used during the tutorial to highlight some htmx and Tailwind features.

Simple Site

If you want to see how to build a site like Music Binder from the ground up (including a little background on FastAPI), then this repo is for you.

The documentation is split into chapters that introduce each section and highlight features during each section of the build.

This repo was used during a workshop with the San Diego Python User Group on April 1, 2023.

Perfect if you want to follow along at your own pace.


In case you want to test either of the repos above, the easiest way to test things out is to use GitHub Codespaces

Instructions on how to do this are in the repo, but it mostly involves pressing a button and waiting a bit.

This opens up the code in your browser window in a dedicated, containerized cloud environment.

No need to install dependencies or clone/copy anything locally.

If you use VSCode, you can also open it up on your desktop application.


For the Music Binder app, I used the following libraries:

That last one is meant to provide a thin database layer that can easily be replace by more feature-rich ORMs or ODMs, in case you want to adapt this for a real-world use case.

FastAPI Stuff


FastAPI provides a way to create Jinja template response with this command:

from fastapi.templating import Jinja2Templates

However, in the tutorial, we use a library called jinja2-fragments as a drop-in replacement for Jinja2Templates.

Instead, we import Jinja2Blocks and otherwise use it the same way.

from jinja2_fragments.fastapi import Jinja2Blocks

Now we can create a templates object which points to the directory where the templates are kept, something like:

templates = Jinja2Blocks(directory="path/to/templates")


Most web frameworks have a way to create routes/views/endpoints. These endpoints contain the logic that generates a response when a user visits a certain url in their browser.

Simple FastAPI applications typcially show these endpoints being defined all in the same place where the app is created (

The Music Binder repo chooses to put all routes in one place ( and then registers them all in where the app is instantiated.

In you should see:

router = APIRouter()

Each subsequent route/view/endpoint is added to this instance of APIRouter and is then "registered" in



One common task when starting up an application is providing "on startup" and/or "on shutdown" events, typically database operations or other build-dependent.

Very recently, FastAPI introduced an elegant way to deal with this by using asynccontextmanager from the standard library.

In practice it looks something like this:

async def lifespan(app: FastAPI):
    # Startup event
    # Shutdown event

This context manager can then be used to define the lifespan parameter of the FastAPI app.

In Music Binder and Simple Site, it is used to run the tailwindcss build command, just in case you forgot to build/compile the css prior to starting the app.

TailwindCSS Stuff

Perhaps the most important resource here, is the Tailwind documentation itself. It is probably more useful than anything I can type here.

I want to highlight two aspects of it, though.


Using Tailwind might mean a paradigm shift in how you think about css. Since you are mostly spending your time in html, you start seeing a lot of patterns being repeated.

You may wonder if it might be a good idea to create your own custom classes to reduce how much code you see "repeating" (must... stay... DRY!!!)

But before you go down that road, read this:

Reusing Styles (TailwindCSS documentation)

If you think you need to create custom classes, keep this in mind:

Components and template partials solve this problem much better than CSS-only abstractions because a component can encapsulate the HTML and the styles. Changing the font-size for every instance is just as easy as it is with CSS, but now you can turn all of the titles into links in a single place too.


The second thing is not to get overwhelmed with the Tailwind classes. They tend to be easy to pick up, over time, but the documentation is pretty dang awesome.

Utilize the Search for either the Tailwind class your looking for, or the CSS property you are curious about.

TailwindCSS Documentation

And, if you use VSCode, use the Tailwind extension (I believe the one for PyCharm is already installed).

This will also help you tremendously.


Okay, fine, a third thing. I pretty much always have the build command set to a watcher, which compiles on save. Sure, it's set to build on app startup, but having that immediate feedback while you're working is invaluable.

tailwindcss -i path/to/input.css -o path/to/output.css --watch


Last, but not least, don't forget to look at the essays on the htmx website:

These provide a lot of the context and reasoning behind this particular approach to building applications, and there are some very compelling cases.

I highly recommend this one:

A Response To "Have Single-Page Apps Ruined the Web?"

Again, usage is very straightforward. You just need to add the appropriate htmx attribute to most any html element, look for that particular request in your application (using the Request header), and send an appropriate response.

From htmx documentation:

<button hx-post="/clicked"
    Click Me!

“When a user clicks on this button, issue an HTTP POST request to ‘/clicked’ and use the content from the response to replace the element with the id parent-div in the DOM”

Production Ready

I've been asked if htmx is production ready.

Now, I'm just some lowly Business Analyst that plays around with python at the wee hours of the night, so don't take my word for it.

But if you're looking for a real world example of using htmx in a production environment, look no further than the DjangoCon EU 2022 talk given by David Guillot from Contexte:

From React to htmx on a real-world SaaS product: we did it, and it's awesome!

Some highlights (which you can also find over at

Of course, your use case may vary, but I'd say that there are plenty of benefits that you would need to consider when making this choice.


Oh, one last thing.

I'm not sure if this was the only PyCon tutorial to ever have an associated playlist...

But if you're down for some indie music that includes some brooding ballads, melancholy musings, instrumental interludes, and other strange things...

You couldn't do worse than checking out my 4.5 hour Music Binder playlist:

Music Binder playlist on Spotify