Build web UIs with Python decorators, compile to HTML, zero JavaScript required
Project description
FastUI — build web UIs with Python decorators, compile to HTML, zero JavaScript required.
Documentation: https://fastui2.readthedocs.io/ru/latest/
Source Code: https://github.com/ndugram/fastui2
FastUI is a modern server-rendered UI library for Python. It brings a decorator-based API — similar to FastAPI, but for building HTML pages — with Pydantic-validated components, URL routing, server-side actions, and a built-in Swagger UI.
Key features:
- Fast — components compile directly to HTML, no template engine overhead. Built-in hot reload for development.
- Simple — define pages as decorated Python functions, return component lists, no HTML templates.
- Typed — full type annotations throughout; all components are Pydantic-validated models with strict validation.
- Zero JS — everything compiles to plain HTML. Buttons with server actions use a lightweight POST mechanism.
- Routed — URL patterns with typed parameters (
/user/{id:int},/post/{year:int}/{slug}). - Interactive — built-in Swagger UI via
/docsto browse and test page routes in the browser. - Extensible — custom CSS, external stylesheets, inline styles, component protocol for custom components.
Requirements
Python 3.10+
FastUI depends on:
pydantic— component model validation and serialization.annotated-doc— parameter documentation viaAnnotated[type, Doc("...")].
Installation
$ pip install fastui2
---> 100%
Example
Create it
Create a file main.py:
from fastui import App, ui
app = App()
@app.page("/")
def home():
return [
ui.heading("FastUI", level=1),
ui.text("Build UIs with Python. No JavaScript required."),
ui.button("About", on_click="/about"),
]
@app.page("/about")
def about():
return [
ui.heading("About", level=1),
ui.text("FastUI compiles Pydantic components to HTML."),
ui.link("Back", url="/"),
]
if __name__ == "__main__":
app.run()
Run it
$ python main.py
Check it
You will see output like:
╔════════════════════════════════════════════╗
║ FastUI Dev Server ║
╠════════════════════════════════════════════╣
║ ║
║ → http://127.0.0.1:8000 ║
║ ║
║ ♻ Hot reload ║
║ ║
║ 📖 Docs http://127.0.0.1:8000/docs ║
║ ║
║ Routes: ║
║ • / ║
║ • /about ║
║ ║
╚════════════════════════════════════════════╝
Open http://127.0.0.1:8000 in your browser.
Interactive API docs
Now go to http://127.0.0.1:8000/docs.
You will see the automatic interactive API documentation with all registered routes:
Each route shows its URL pattern, summary, parameters, and response schema:
Routes with path parameters ({id:int}, {slug}) have input fields for testing:
Upgrade the example
Now modify main.py to get more out of FastUI. Each upgrade below builds on the previous one.
With typed URL parameters...
Add a route with an integer parameter:
@app.page("/user/{id:int}", title="Profile")
def user_profile(id: int):
return [
ui.heading(f"User #{id}", level=1),
ui.text(f"Profile page for user {id}."),
ui.link("Back", url="/"),
]
Visit http://127.0.0.1:8000/user/42. The id parameter is automatically converted to int.
With server actions (POST handlers)...
Buttons can call Python functions on the server via POST:
counter = 0
def increment() -> list:
global counter
counter += 1
return [
ui.heading(f"Count: {counter}", level=1),
ui.button("+1", on_click=increment),
ui.link("Back", url="/counter"),
]
@app.page("/counter")
def counter_page():
return [
ui.heading("Counter", level=1),
ui.text(f"Value: {counter}"),
ui.button("+1", on_click=increment),
]
When on_click receives a callable, the framework registers it as a POST endpoint and
replaces it with the action URL before rendering.
With custom CSS...
Pass custom CSS to the App constructor:
CUSTOM = """
body { background: #1a1a2e; color: #e0e0e0; }
h1 { color: #e94560; }
button { background: #e94560; color: #fff; border: none; }
"""
app = App(css=CUSTOM)
Or use external stylesheets:
app.stylesheets = [
"https://cdn.jsdelivr.net/npm/bootstrap@5.3/dist/css/bootstrap.min.css",
]
With OpenAPI tags...
Group routes in the Swagger UI with tags:
@app.page("/users", title="Users", tags=["users"])
def users():
return [ui.heading("Users", level=1)]
@app.page("/items", title="Items", tags=["items"])
def items():
return [ui.heading("Items", level=1)]
Tags appear as a filter in the Swagger UI header.
With a multi-page layout...
Share navigation across pages with a helper function:
def nav() -> ui.page:
return ui.page([
ui.link("Home", url="/", style="margin-right: 1rem;"),
ui.link("Blog", url="/blog", style="margin-right: 1rem;"),
ui.link("About", url="/about"),
], style="padding: 1rem; background: #f0f0f0; border-radius: 8px; margin-bottom: 1rem;")
@app.page("/")
def home():
return [nav(), ui.heading("Home", level=1), ui.text("Welcome!")]
@app.page("/about")
def about():
return [nav(), ui.heading("About", level=1), ui.text("FastUI details.")]
Components
All built-in components are available through the ui builder:
| Component | Builder | HTML |
|---|---|---|
| Heading | ui.heading("text", level=1) |
<h1>text</h1> |
| Text | ui.text("content") |
<p>content</p> |
| Button | ui.button("label", on_click=...) |
<button>label</button> |
| Input | ui.input(label="Name") |
<label>Name<input></label> |
| Link | ui.link("text", url="/") |
<a href="/">text</a> |
| Code | ui.code("code") |
<pre><code>code</code></pre> |
| Divider | ui.divider() |
<hr> |
| Page | ui.page([...]) |
<div>...</div> |
Every component accepts optional styling:
ui.heading("Styled", level=2, style="color: red;")
ui.button("Big", class_name="btn-lg", style="padding: 1rem;")
ui.text("Centered", style="text-align: center;")
Routing
Routes map URL patterns to handler functions. Patterns support typed parameters:
| Pattern | Example URL | Handler receives |
|---|---|---|
/ |
/ |
— |
/about |
/about |
— |
/user/{id:int} |
/user/42 |
id=42 (int) |
/hello/{name} |
/hello/world |
name='world' (str) |
/post/{year:int}/{slug} |
/post/2025/hello |
year=2025, slug='hello' |
Routes are registered in order; the first match wins.
Server actions
Buttons can call server-side Python functions via POST:
def handle_click() -> list:
return [
ui.heading("Clicked!", level=2, style="color: green;"),
ui.link("Back", url="/"),
]
@app.page("/")
def index():
return [
ui.button("Click me", on_click=handle_click),
]
The framework automatically:
- Registers the callable as a POST endpoint at
/_ui/action/<id> - Replaces the callable with the action URL in the rendered HTML
- On click, the browser POSTs to the action URL
- The handler runs and returns new components rendered as HTML
OpenAPI documentation
FastUI auto-generates OpenAPI 3.0 schema for all routes. Available at /docs (Swagger UI)
and /openapi.json by default.
app = App(
title="My API",
version="2.0.0",
description="API description in **Markdown**.",
docs_url="/api-docs",
openapi_url="/api-schema.json",
)
Disable docs:
app = App(docs=False)
Hot reload
Enable auto-refresh on file changes:
app.run(hot_reload=True)
The server polls .py files in the current directory and the fastui package directory.
On change, the browser refreshes automatically.
Examples
See the examples directory for 20 complete, runnable programs:
| # | Example | What it shows |
|---|---|---|
| 1 | hello_world | Minimal app — one page, one heading |
| 2 | all_components | Every built-in component type |
| 3 | route_params | URL patterns with typed parameters |
| 4 | server_actions | POST callback handlers |
| 5 | custom_css | Dark theme with custom CSS |
| 6 | forms | Input fields and form layout |
| 7 | navigation | Multi-page navigation with links |
| 8 | docs_config | Custom OpenAPI docs metadata |
| 9 | layout_page | Page component for grouping |
| 10 | counter | Interactive counter with actions |
| 11 | todo | Simple todo application |
| 12 | hot_reload | Hot reload demo |
| 13 | multi_page | Shared navigation layout |
| 14 | no_docs | Running without docs |
| 15 | external_stylesheets | Bootstrap integration |
| 16 | single_component | Returning single vs list |
| 17 | advanced_routing | Complex multi-param routes |
| 18 | inline_styles | All style combinations |
| 19 | minimal | Absolute minimal app (7 lines) |
| 20 | dynamic_routes | Dynamically generated routes |
FAQ
-
Why would I use FastUI instead of Flask + Jinja2? FastUI eliminates the template layer — you write UIs entirely in Python without HTML files. It's ideal for small to medium apps where the overhead of a template engine isn't justified.
-
Why would I use FastUI instead of Streamlit? FastUI gives you explicit control over routing, URL parameters, and page structure. Streamlit is script-based and re-runs everything on every interaction; FastUI uses traditional request-response with proper URL routing.
-
Does FastUI support async handlers? Not yet. Handlers are synchronous. Async support is planned.
-
Can I use FastUI with an existing HTTP server? The
Appclass runs its own dev server. For production, you'd wrap it in ASGI/WSGI — this is on the roadmap. -
Does FastUI support WebSockets? Not currently. Server-sent events and WebSocket support may be added later.
-
Can I write my own components? Yes. Any object with a
to_html()method satisfies theComponentprotocol. Pydantic models withto_html()work seamlessly. -
Is FastUI production-ready? FastUI is in early development (v0.1.0). The API may change. It's suitable for internal tools and prototypes but not yet for customer-facing production apps.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fastui2-0.3.0.tar.gz.
File metadata
- Download URL: fastui2-0.3.0.tar.gz
- Upload date:
- Size: 24.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
49986a7424bc72f50070e05fcb5db360e6393e097e48c994ad9fa7016ae5e8b5
|
|
| MD5 |
53c6206232098e221e91e71af0d1f314
|
|
| BLAKE2b-256 |
37d112b0aa2270b9b87cf3b171855acce6530b70040d3300fbec21f76f6f0189
|
File details
Details for the file fastui2-0.3.0-py3-none-any.whl.
File metadata
- Download URL: fastui2-0.3.0-py3-none-any.whl
- Upload date:
- Size: 21.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a8230b88cba60aad411fb34083e9f218fe08a2fb7ba075499a31237c495a69a4
|
|
| MD5 |
cd889aa6c9416e26fe410a8018c30f0c
|
|
| BLAKE2b-256 |
d5a1d31302def78724428f8dcd0e3704d4ccc412988cc150fd3f1c3ad4600105
|