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
- Fine-grained reactivitySignals, memos and effects. A change touches the DOM nodes that read it and nothing else.
- TemplatesPython 3.14 template strings,
html(t"…"), or a builder withwithblocks. Both clone once and bind only the holes. - Control flow and async
Show,For,Switch;ResourceandActionwithLoadingandErroredboundaries. - A nested routerThe URL drives state. Plain links work; a level whose route did not change keeps its state.
- Two runtimesMicroPython when the download matters, Pyodide when you want the standard library and PyPI.
- No build stepServe the files.
mk exportwrites a directory that runs anywhere.
How small
The counter above, complete, as the browser downloads it:
| Runtime | Transferred | Compressed |
|---|---|---|
| MicroPython | 0.84 MB | 0.32 MB |
| Pyodide | 13.8 MB | 6.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.