Frontage

A Python frontend framework for the browser.

Reactive components, an SPA router and two-way data binding, running on PyScript over WebAssembly. No JavaScript, no Node, no bundler. You write Python and the browser runs it.

from frontage import Signal, component, html, mount


@component
def counter(initial=0):
    count = Signal(initial)

    def inc(ev):
        count.update(lambda n: n + 1)

    return html(t"""
        <div class="counter">
            <button on:click={inc}>+</button>
            <span>Value: {count}</span>
        </div>
    """)


mount(lambda: counter(initial=0), "#app")

Signals hold state. Anything callable inside a template is a hole that updates in place when what it read changes. That is the whole model: no re-render, no diff, no DSL.

What you get

How small

The counter above, complete, as the browser downloads it:

RuntimeTransferredCompressed
MicroPython0.84 MB0.32 MB
Pyodide13.8 MB6.4 MB

Frontage itself is 131 KB of Python, 39 KB compressed. The rest is the interpreter. Measured on 2026-09-05 with PyScript 2026.7.3; the benchmark and its numbers are in the repository.

See it run

The examples run live on this site, on MicroPython by default (add ?type=py for Pyodide). They are the same files the browser test suite drives: a counter, a todo list, the benchmark table, data fetching, a form, and a routed app.

Try it

The playground runs whatever you type against the package on this site, on MicroPython, and puts the code in the link so a snippet can be shared or handed to a class.

Install

Frontage is a client-side library: installing it means telling PyScript where the wheel is. The documentation walks through a first project.

{
  "packages": ["https://frontage.optersoft.com/dist/frontage-0.2.0-py3-none-any.whl"]
}

That pyscript.json is the whole install. The wheel is also on PyPI.