Library / SDK
lona-web-org/lona avatar
lona-web-org/lona

Lona writes responsive web apps entirely in Python, no JavaScript required

Write responsive web apps in full python

553 stars27 forksPythonMIT

At a glance

What is it?
Lona is a Python web application framework that handles both the server side and the browser side through a small JavaScript library speaking a custom protocol, so click handlers and live content updates are written in Python. It suits a small team that wants one language across the stack, and it asks you to accept a homegrown rendering model rather than a standard one.
Who is it for?
Adopt Lona if you want a web application written in one language, if you are comfortable owning a small framework rather than assembling one from parts, and if your application does not need to be consumed by a JavaScript frontend team. Do not adopt it if you need a large third-party component ecosystem, a public API consumed by a separate SPA, or a job market you can hire from, since the framework is a single maintained project with a release from March 2024.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 132 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The gap Lona fills: Python on the server, JavaScript on the client

The README frames the problem in plain terms. Web is a solved problem in Python since ages, but traditionally Python handles only the server side, so if you want client side interaction like click events, or you want to update content live, you have to write an additional JavaScript application. Lona handles the server side and the client side and provides a simple, pythonic API to write self contained views. That framing places the project precisely. It is not a Django alternative in the usual sense of a different ORM or admin interface; it is a different answer to a different question, namely how much JavaScript a Python developer has to accept in order to have an interactive page. The project describes itself as a web application framework designed to write responsive web apps in full Python, and the topics list it under framework, python3, web and hacktoberfest. The licence is MIT, the primary language is Python, and the repository is not archived with the last push on 2026-06-13. The release history is short and slow: 1.16.2 on 2024-03-25, 1.16.1 in November 2023 and 1.16 in October 2023.

How the browser and the server actually talk

The README answers the how does it work question with a paragraph, and it is worth reading closely because it explains why Lona looks the way it does. Lona comes with a Javascript based browser library that speaks a specialized protocol with the backend, and the protocol specifies messages like hey frontend, please show $HTML and hey backend, someone clicked on node XY. So the browser library is not a general purpose frontend framework that happens to be written in JavaScript; it is a thin client for a bespoke wire protocol between a Lona backend and a browser tab. Two consequences follow. First, there is no HTML page for the server to hand over and then patch up with a script framework, because the server sends instructions about which HTML to show and the client applies them. Second, the model is symmetrical: the server pushes HTML to the frontend, and the frontend pushes node identity and events back to the server, so a click handler in Python receives an event that refers to a specific node in the tree it built. The protocol is the contract, and the documentation pages for the basic concept and the getting started tutorial at lona-web.org are where its details live.

A view class, a route and a handler

The example in the README is small enough to read as a complete picture of the programming model. A LonaApp is created with the current file, routes are declared with a decorator on a class, and the class extends LonaView. The view builds an HTML tree from imported node classes, and it receives two kinds of callbacks: handle_request for the initial render, and handle_button_click for the event, with the handler wired to the node through a keyword argument on the Button constructor.

python
    from lona.html import HTML, Button, Div, H1
    from lona import LonaApp, LonaView

    app = LonaApp(__file__)


    @app.route('/')
    class MyView(LonaView):
        def handle_button_click(self, input_event):
            self.message.set_text('Button clicked')

        def handle_request(self, request):
            self.message = Div('Button not clicked')

            html = HTML(
                H1('Click the button!'),
                self.message,
                Button('Click me!', handle_click=self.handle_button_click),
            )

            return html

Two details in that snippet carry the design. The view keeps a reference to a node it created, here self.message, and the click handler mutates that node rather than rebuilding the page, which is how a live update stays cheap and how the state survives between events. And the run call at the bottom of the file turns on live reload.

python
    if __name__ == '__main__':
        app.run(port=8080, live_reload=True)

The installation step is the standard pip install for the package published on PyPI as lona.

bash
    # pip install lona

Dependencies, Python versions and the packaging shape

The pyproject.toml tells you what Lona depends on and what it promises. The build backend is setuptools with requires of setuptools>=61.0, the package name is lona, and requires-python is >=3.8. The runtime dependencies are short and tell you the architecture: aiohttp for the async server, watchfiles for the live reload file watching, jinja2 for templates, rlpython for the interactive shell, and typing-extensions. The classifiers place it under Framework :: AsyncIO, so the async story is the aiohttp one, and the package is marked as Python 3 only. Optional dependency groups are declared for packaging with build and twine, for dev with tox, for test, and for lint. The test group is the most informative part about how the project is checked: coverage 7.6.1, pytest 8.3.3, pytest-aiohttp, pytest-mock, pytest-timeout, pytest-asyncio below version 1, and playwright 1.58.0, which means the browser side of the protocol is tested with a real browser rather than mocked. The lint group pins flake8 7.1.1 with a long list of plugins covering bugbear, commas, comprehensions, length, logging format, mutable defaults, noqa usage, pytest style, quotes and f-strings, plus mypy 1.11.2 and isort 5.13.2.

The Docker and tox path for running the test suite

The Makefile and docker-compose.yml show how the project runs its own checks, and the mechanism is worth knowing if you want to contribute. The Makefile defines PYTHON as python3.11 and creates a virtual environment called env, installing the package in editable mode with pip install -e .[dev,packaging]. For tests it does not call pytest directly. Instead it shells out to Docker Compose and runs tox inside a container built from playwright.Dockerfile, and the compose file defines a bridge network, a service named playwright that runs as user 1000:1000, builds with UID and GID build args defaulting to 1000, mounts the repository at /app, and sets PLAYWRIGHT_BROWSERS_PATH to /ms-playwright. The working directory is /app and the command is tox.

yaml
services:
    playwright:
        user: 1000:1000

        build:
            context: ./
            dockerfile: playwright.Dockerfile
            args:
                - UID=${UID:-1000}
                - GID=${GID:-1000}

The Makefile target for a full test run is one line.

bash
test:
	$(call DOCKER_COMPOSE_RUN, playwright, tox ${args})

The CI variant runs a matrix of environments instead of the default one, naming py38, py39, py310 and py311 alongside lint, which lines up with the requires-python floor of 3.8 and shows the supported version range being exercised on every change. The compose file is also why playwright.Dockerfile sits at the top level of the repository, since it is the image definition for the browser test environment.

What Lona is not, and the trade you are making

The honest assessment is that Lona replaces a category of dependency with a single one. A conventional Python web stack pulls in a template layer, a client framework and usually a build step, and the browser ends up running code you shipped from a bundle. Lona replaces all of that with a browser library that speaks one protocol, and the payoff is that a Python developer can write the entire application, with one language, one runtime and one deployable process. The cost is that the ecosystem you give up is large. Jinja2 is still a dependency, but the broader Jinja ecosystem, the JavaScript component ecosystem, and the tooling around it are not part of this design. The second cost is the wire protocol itself. Because the frontend is a Lona client rather than a general purpose browser platform, anything Lona does not implement is yours to build: a component that is not in lona.html, or a browser API that the client library does not wrap. The third cost is the scale of the community. The repository is a single project with one main author listed in pyproject.toml, Florian Scherf, releases come infrequently, and the most recent tagged version is 1.16.2 from March 2024 even though the repository was pushed to on 2026-06-13, so the gap between published releases and repository activity is wide. A team that wants a framework with a large hiring pool and a deep package registry should read that gap as a fact about the risk, not as a preference.

Where Lona is the wrong tool

Three situations rule it out. The first is a project where the frontend is a separate product. If a JavaScript team owns the interface and you need to serve it, Lona's protocol has nothing to offer, since its client library exists to be driven by a Lona backend rather than to interoperate with an arbitrary SPA. The second is a public API project. Lona is built for HTML rendered to a browser, and a JSON API for third-party consumers is a different shape of work where a framework with request routing and content negotiation already solved the problem. The third is a team that needs depth of third-party components, since Lona is not a widget library and each missing piece is a piece you write. There is also a maintenance point specific to this repository. The Makefile still calls docker-compose with the hyphenated form while the compose file itself is a version 3 definition, and the tox matrix references py38 and py39 environments, both of which are end of life upstream. Those are small signals, but they say the same thing as the release dates: this is a project in a slow phase of its life, and adopting it means owning it. The documentation site at lona-web.org, the changelog page and the Discord are where the project says to look, and the FAQ page is a reasonable place to check whether your question is already answered before you commit.

Editorial conclusion

Adopt Lona if you want a web application written in one language, if you are comfortable owning a small framework rather than assembling one from parts, and if your application does not need to be consumed by a JavaScript frontend team. Do not adopt it if you need a large third-party component ecosystem, a public API consumed by a separate SPA, or a job market you can hire from, since the framework is a single maintained project with a release from March 2024. Verify two things before committing: that your Python version satisfies requires-python of 3.8 or newer, and that the live update mechanism suits your UI, because the browser library and the backend protocol are the design centre of the project and everything else follows from them. The practical first step is to pip install lona, write a single LonaView with one button handler, and run it with app.run(port=8080, live_reload=True) to see the round trip work before you build a larger view.

Frequently asked questions

What is Lona?

Lona is a Python web application framework for writing responsive web apps in full Python. It handles both the server side and the client side, providing a simple pythonic API to write self contained views, and it is MIT licensed.

Does Lona really avoid JavaScript?

Lona ships a Javascript based browser library, but it is a client for a specialized protocol with the backend rather than a general purpose frontend framework, so views, click handlers and live updates are written in Python. The README describes the protocol as messages like show this HTML and someone clicked on node XY.

How do I install Lona and run my first app?

Install the package from PyPI with pip install lona, then create a LonaApp, decorate a LonaView subclass with @app.route, build an HTML tree in handle_request and wire a handler with the handle_click keyword argument on a node. Run the file with app.run(port=8080, live_reload=True) and open port 8080.

Which Python versions and packages does Lona need?

The package requires Python 3.8 or newer and depends on aiohttp, watchfiles, jinja2, rlpython and typing-extensions. It is built with setuptools 61.0 or newer, and the test extra pulls in pytest, coverage and playwright.

How is the Lona test suite run?

The Makefile runs tox inside a Docker Compose service built from playwright.Dockerfile, with the repository mounted at /app and the playwright browsers installed at /ms-playwright. The make test target runs tox in that container, and the CI target adds lint and the py38, py39, py310 and py311 environments.

Official sources

  1. Issues
  2. License: MIT
  3. lona-web-org/lona on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/lona-web-org-lona.svg)](https://hysenlabs.com/projects/lona-web-org-lona)

Was this useful?