Using Tailwind CSS
Style Nitro pages with Tailwind utility classes, from local dev to production builds.
Nitro writes plain HTML, so Tailwind works with it out of the box. Tailwind finds the classes you use by reading your source files as text, and that includes Python. Every cls="..." string in a Nitro UI page gets picked up like it would in a template.
The setup is short. Compile Tailwind into src/styles/, and Nitro copies that folder to /assets/styles/ on every build.
Install Tailwind
From your project root:
npm install -D tailwindcss @tailwindcss/cli
Point Tailwind at your source
Create tailwind.css in the project root:
@import "tailwindcss" source("./src");
source("./src") limits the scan to your source folder, so Tailwind never reads build/ or node_modules/.
Use Tailwind classes in a page
Link the compiled stylesheet in your Head and pass Tailwind classes with cls:
from nitro import Page
from nitro_ui import (
HTML, Head, Body, Title, Meta, Link, Main, H1, Paragraph, Anchor, Div,
)
def card(title, body):
return Div(
Div(title, cls="font-semibold text-zinc-900"),
Paragraph(body, cls="mt-1 text-sm text-zinc-600"),
cls="rounded-xl border border-zinc-200 bg-white p-6 shadow-sm",
)
def render():
return Page(
title="Home",
content=HTML(
Head(
Meta(charset="utf-8"),
Meta(name="viewport", content="width=device-width, initial-scale=1"),
Title("Home"),
Link(rel="stylesheet", href="/assets/styles/tailwind.css"),
),
Body(
Main(
H1(
"Built with Nitro",
cls="text-4xl font-bold tracking-tight text-zinc-900",
),
Paragraph(
"Python pages, Tailwind styles.",
cls="mt-3 text-lg text-zinc-600",
),
Div(
card("Fast", "Pre-built static HTML."),
card("Simple", "No templates, just functions."),
cls="mt-8 grid gap-4 sm:grid-cols-2",
),
Anchor(
"Get started",
href="/quickstart",
cls=(
"mt-8 inline-block rounded-lg bg-blue-600 px-5 py-2.5 "
"font-medium text-white hover:bg-blue-700"
),
),
cls="mx-auto max-w-3xl px-4 py-16",
),
cls="bg-zinc-50 antialiased",
),
),
)
Run it while you develop
Run Tailwind in watch mode in one terminal:
npx @tailwindcss/cli -i tailwind.css -o src/styles/tailwind.css --watch
And the Nitro dev server in another:
nitro dev
When you save a page, Tailwind rewrites src/styles/tailwind.css and the dev server reloads with the new styles.
The compiled file is generated, so keep it out of git:
src/styles/tailwind.css
Compile on nitro build
You can make nitro build compile Tailwind for you with a plugin. Nitro loads plugins from src/plugins/<name>.py and looks for a class called Plugin. The nitro.pre_build hook runs before any pages are generated, so the CSS is ready by the time Nitro copies your styles.
Create src/plugins/tailwind.py:
import subprocess
import sys
from pathlib import Path
from nitro.plugins import NitroPlugin, hook
PROJECT_ROOT = Path(__file__).resolve().parents[2]
class Plugin(NitroPlugin):
name = "tailwind"
version = "1.0.0"
@hook("nitro.pre_build")
def compile_css(self, data):
result = subprocess.run(
[
"npx", "@tailwindcss/cli",
"-i", "tailwind.css",
"-o", "src/styles/tailwind.css",
"--minify",
],
cwd=PROJECT_ROOT,
)
if result.returncode != 0:
sys.exit("Tailwind failed to compile, so the build was stopped.")
return data
Then turn it on in nitro.config.py:
from nitro import Config
config = Config(
site_name="My Site",
plugins=["tailwind"],
)
If Tailwind fails, sys.exit() stops the build with an error, so you never deploy pages without their styles. Raising an exception wouldn't work here: Nitro ignores exceptions raised inside a plugin hook and carries on building. The plugin guide explains why.
Deploying from CI
The plugin needs Node, so your CI job has to install it alongside Python. On GitHub Actions:
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- uses: actions/setup-node@v4
with:
node-version: "22"
- run: pip install -r requirements.txt
- run: npm ci
- run: nitro build
If you'd rather not run Node in CI, skip the plugin, compile locally with --minify and commit src/styles/tailwind.css instead.
Things to watch for
Write class names out in full
Tailwind reads your files as plain text and never runs your Python. It can't see a class you build at runtime:
# Tailwind never sees bg-blue-600 or bg-red-600 here
Div(cls=f"bg-{color}-600")
Map values to complete class names instead:
BUTTON_COLORS = {
"primary": "bg-blue-600 hover:bg-blue-700",
"danger": "bg-red-600 hover:bg-red-700",
}
Div(cls=BUTTON_COLORS[color])
Mixing with StyleSheet and Theme
Tailwind and Nitro UI's own StyleSheet can sit side by side. A common split is Tailwind for layout and spacing, with StyleSheet and Theme for a few components that need design tokens. Link both stylesheets in your Head.
When both set the same property on an element, the StyleSheet rule wins whichever order you link them in. Tailwind v4 puts its utilities in a cascade layer, and plain CSS outside a layer always beats layered CSS.