Build static sites with Python.
Templates are a second language to learn and debug. A Nitro page is a Python function that returns HTML, and the build is a folder you can host anywhere.
$ pip install nitro-clifrom nitro_ui import Body, Div, H1, H2, Paragraph, Section
from nitro import Page
FEATURES = [
("No templates", "Pages are Python functions."),
("No config", "Sensible defaults, zero setup."),
]
def render():
return Page(
title="Hello",
content=Body(
H1("Hello, World!"),
Section(
*[
Div(H2(name), Paragraph(text), cls="card")
for name, text in FEATURES
],
cls="features",
),
),
)<!doctype html>
<html>
<body>
<h1>Hello, World!</h1>
<section class="features">
<div class="card">
<h2>No templates</h2>
<p>Pages are Python functions.</p>
</div>
<div class="card">
<h2>No config</h2>
<p>Sensible defaults, zero setup.</p>
</div>
</section>
</body>
</html>Six tools. Install one.
Each package stands on its own. Take Nitro UI for HTML inside a Flask app and leave the rest of the toolchain behind.
Build static sites with Python code instead of template engines. Live reload, incremental builds, deploy anywhere.
Create HTML with Python classes instead of strings. Type-safe, autocomplete-friendly, zero dependencies.
Read JSON with dot notation and a chainable query builder. No schema to define, no ORM to learn.
Add plugins to any Python app. Async-ready hooks with priorities and error isolation built in.
Validate data in one line. 51+ built-in rules, zero dependencies, and pipe-string rules like required|email.
Resize, convert, and optimise images with a chainable API. Generate responsive sizes in one call.
Three concepts.
No build config and no magic. If you can write a Python function, you can write a page.
def render():
return Page(
title="Hello",
content=Body(H1("Hello, World!")),
)Pages are functions.
Loops, conditionals, imports, f-strings: everything Python already does, instead of a template dialect that reimplements half of it.
Card(
H2("Welcome"),
Paragraph("Get started in seconds."),
cls="hero-card"
)Components are classes.
Nest them, reuse them, pass data as keyword arguments. A misspelled element is a NameError at build time, not a silently empty div in production.
data = DataStore.from_file("posts.json")
for post in data.posts:
Card(title=post.title)Data is dot notation.
Point it at a JSON file and read data.posts straight off it. No schema to define first, and no chain of dictionary keys to get wrong.
Scaffold, watch, build.
Live reload while you work, incremental builds that skip what hasn't changed, and a plain folder of HTML at the end.
Python is all you need.
No webpack. No template languages. No JavaScript required. Just Python functions that return HTML.
Read the docs