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.
Python 3.10+
FastUI depends on:
pydantic— component model validation and serialization.annotated-doc— parameter documentation viaAnnotated[type, Doc("...")].
$ pip install fastui2
---> 100%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()$ python main.pyYou 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.
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:
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.")]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;")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.
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
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)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.
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 |
-
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.


