Skip to main content

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.

Tests Package version Python Monthly downloads Total downloads Pydantic v2


Documentation: https://fastui.ndugram.dev/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 /docs to 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 via Annotated[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:

  1. Registers the callable as a POST endpoint at /_ui/action/<id>
  2. Replaces the callable with the action URL in the rendered HTML
  3. On click, the browser POSTs to the action URL
  4. 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 App class 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 the Component protocol. Pydantic models with to_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

fastui2-0.2.0.tar.gz (24.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fastui2-0.2.0-py3-none-any.whl (21.6 kB view details)

Uploaded Python 3

File details

Details for the file fastui2-0.2.0.tar.gz.

File metadata

  • Download URL: fastui2-0.2.0.tar.gz
  • Upload date:
  • Size: 24.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for fastui2-0.2.0.tar.gz
Algorithm Hash digest
SHA256 411719a66b2cf76ef1777d96617c5793bc75c24e85fefddf4a3bc747d64fc0de
MD5 90d92caeb2ccf649223ed012ec85af92
BLAKE2b-256 6a49ada8bc32977a28d9578d5b84c57f0bee49ce144a38d47f57539d0ebe9419

See more details on using hashes here.

File details

Details for the file fastui2-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: fastui2-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 21.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for fastui2-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1df201c98632e5c2da553bfe6ee47cbcb416b728127908b55135c2063fb17ae7
MD5 827e0183cddccc4740e92eb65403335f
BLAKE2b-256 314e5aec5e5c274fa908efb44579b967450c9fc6be2cca91a58dfdf912c38a2c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page