Skip to main content

Oxid

documentation · PyPI

A fine-grained reactive UI framework for Python in the browser. Signals, memos and effects; templates that clone once and bind only their holes; a keyed For; a nested router; running on its own Python runtime compiled to WebAssembly, with the framework delivered as precompiled bytecode. No JavaScript, no Node, no bundler: you write Python and the browser runs it.

Status: 0.17, alpha. The API is young and will move. What is in it, by release: the reactive core, templates (h and html(t"…")), control flow, a nested router and widgets (0.2–0.3); prerendering with hydration (0.4); transitions and async memos (0.5–0.7); oxid serve (0.8); oxid's own Python runtime, written in Rust (0.10), with the reactive graph, the DOM operations and the template path native inside it; islands and static pages (0.11); content collections (0.12); oxid site and locales (0.13); oxid-server, the HTTP server on the same runtime (0.14–0.17: routing and policies matched in Rust, sessions, uploads, auth, WebSockets, OpenAPI, and the page rendered per request with islands); declarative charts on SVG or a canvas (0.15). Since 0.16 the wheel for your machine carries the compiler and the server binary, so pip install oxid is the whole install. The browser suite runs every example in Chromium before a release.

from oxid import Signal, component, html, mount


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

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

    def dec(ev):
        count.update(lambda n: n - 1)

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


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

Signals hold state; anything callable in a template is a hole that updates in place when what it read changes; a component body runs once. Name the functions you put in holes: a lambda inside a template's braces is hard to read and oxid check will tell you so.

Try it at frontage.optersoft.com/playground, which runs your code in the browser and keeps it in the link. Learn it at academy.optersoft.com/python/oxid, twenty-five chapters, thirteen of them with an app published on GitLab Pages. Install it with pip, and oxid build writes a directory that runs anywhere:

uvx oxid build myapp        # index.html, your .py, and _oxid/ beside them

A model writing Oxid code for you can read frontage.optersoft.com/llms.txt: what the framework is, the rules it most often gets wrong (the runtime is a subset of Python, templates are t-strings, holes are named functions), and every chapter of the course.

The counter, cold cache 0.10.0 0.9.1 (MicroPython) 0.8.3 (PyScript)
requests 17 6 29
transferred 0.78 MB 0.46 MB 0.91 MB
compressed 0.32 MB 0.19 MB 0.33 MB
to first paint 23 ms 59 ms 88 ms

Most of that is the runtime, 261 KB of gzip; the rest is one file per module of bytecode, each named by its content so a host can cache it forever, and none of it is parsed in the browser. It is a bigger download than MicroPython's and a much faster page: the runtime starts in a few milliseconds and builds a thousand rows in 24.8 ms against 71.1 (project/plan/2026-09-08-runtime.md §9). Medians of five, this laptop's Chromium, measured at 0.10.0; project/design/architecture.md §12 has the method.

The same package is a small command line on your machine, stdlib only:

uvx oxid serve              # a dev server that swaps a changed module into the live page
uvx oxid check app.py       # the rules the browser enforces and CPython does not
uvx oxid tailwind           # Tailwind CSS: the standalone CLI, fetched once, no Node
uvx oxid prerender . --out build --crawl   # every route the pages link to, as finished HTML

(uvx runs the oxid script straight from PyPI; pip install oxid puts the same oxid command on PATH, and python -m oxid is the same thing.)

prerender runs the app on your machine, waits for its resources and async memos, and writes each route as finished HTML with the values embedded. In the browser mount hydrates: it adopts the HTML already on screen instead of building it, skips the fetches the page already holds, and replays the clicks made before Python was ready. Static hosting only, no server: Leptos's async rendering mode as a build step.

A page with nothing to run should download nothing to run it. mount(view, "#app", when="never") says so: prerender writes the HTML and no boot tag, so a content page is its own bytes and stops there. What is interactive on it is an island —

from oxid import h, island, mount


def page():
    return h.main(
        h.article(...),  # static: rendered once, at build
        island(theme_toggle, when="idle"),  # alive when the browser is free
        island("charts:sparkline", when="visible"),  # its own chunk, fetched when seen
    )


mount(page, "#app", when="never")

— and the first trigger to fire boots the runtime once, shared by every island on the page. examples/islands is the whole of it in forty lines.

What such a page says comes from a collection: a directory of Markdown whose front matter is checked by the same oxid.schema record that checks a form, rendered once, on your machine, and shipped as HTML.

from oxid.content import collection
from oxid.schema import iso_date, record, text

Post = record(("title", text(min=1)), ("date", iso_date()), ("summary", text(), None))
posts = collection("posts", Post)  # content/posts/*.md, newest first

for post in posts.entries():
    post.slug, post.data["title"], post.view()

A file whose front matter does not match fails the build, naming the file and the field, and a ::: island widgets:reactions when="visible" container in a post is an island where it stands. pip install "oxid[content]"; examples/blog is a blog in one page.

A whole site is a directory, and the directory is the site map:

site/
  pages/index.py          → /
  pages/about.py          → /about/
  pages/blog/index.py     → /blog/
  pages/blog/[slug].py    → /blog/<slug>/, one per static_paths()
  pages/sitemap.xml.py    → /sitemap.xml, a module with a get()
  layouts/site.py         a component taking children; no new concept
  content/posts/*.md      the collection above
  public/                 copied as it is

oxid site renders every page on your machine and writes it where its path says. A page with nothing interactive on it carries no script at all; the runtime is written once, beside the pages, only if some page has an island. oxid serve --prerender is the same build with a file watcher in front of it. examples/site is seven pages, six of which fetch nothing.

A parameter can be a directory, so pages/[lang]/blog/[slug].py is a site in as many languages as LOCALES in site.py names — the first of them at /, the rest under /es/, /ca/. oxid.i18n writes the hreflang alternates and the language switcher, and the switcher is plain links: the build made every page, so site.translate(path, "es") is an answer rather than a guess, and a reader changing language waits for a document instead of a runtime. examples/locales is twelve pages in three languages that fetch nothing at all.

A page that needs a server has one in the same package. oxid-server is Rust (axum) around the same runtime, and a route is a Python function:

from oxid_server import App, Policy, SessionAuth

app = App(static="www")


@app.get("/notes/{note_id}", policy=Policy(auth=SessionAuth("user"), limit="30/min"))
async def note(note_id: int, user):
    return {"id": note_id, "by": user}
oxid-server app.py          # routes, /openapi.json and /docs, the files in www/
oxid new server notes       # an app, its two kinds of test, and a Dockerfile

Matching, limits, body sizes, sign-in and CSRF are checked in Rust before Python runs; the same oxid.schema record that checks a form in the page validates its post; page(view) renders a view per request, with an island where something is interactive. --runtime cpython runs the same file with the handlers in CPython, for an app that needs pandas.

Tailwind with no build at all: the playground loads Tailwind's browser build, so utility classes work as you type. The Style, Ship and Prerender chapters cover all of it.

Why

Python in the browser exists — CPython and MicroPython both compile to WebAssembly — and the frameworks that use it either carry a server into the browser (Streamlit's stlite, Shiny's Shinylive: tens of seconds to start) or rebuild and diff the page on every change. Oxid is browser-first: no session, no transport, no DSL, and a state change touches only the DOM nodes that read it. Its runtime is its own, because the framework's hot paths are inside it: project/plan/2026-09-08-runtime.md in the source distribution is why, with the measurements that decided it. The whole reasoning, with the frameworks it learned from, is in project/design/architecture.md; the behaviours it must have, one line each, are in project/design/spec.md.

Develop

The repo uses uv and mkrun (mk).

mk sync                 # .venv with every dependency group
mk check                # lint, types, unit tests: the gate
mk runtime.build        # build the runtime from rust/ into oxid/_runtime/ (cargo, wasm-opt)
mk serve                # examples and playground at http://127.0.0.1:8000/, package read live, reload on save
mk test --browser       # every example in Chromium, on the runtime in WebAssembly
mk build examples/todo  # a static directory that boots from WebAssembly
cargo test --profile native   # in rust/: the runtime's own tests, against CPython
mk site.deploy          # publish frontage.optersoft.com by hand (Cloudflare Pages): landing page, gallery, playground, wheels

Without mk: uv sync --all-groups, uv run pytest, uv run ruff check, uv run ty check.

Contributing

Oxid is proprietary and developed by Optersoft; it does not take outside contributions. The rewrite is clean-room: code is written from project/design/spec.md, not from any reference framework's source, and a contribution says so.

License

Proprietary freeware, copyright Optersoft, S.L. See LICENSE and NOTICE. You may use Oxid free of charge for any purpose, including commercial work, and ship the runtime a build puts into your app as part of that app; Oxid itself may not be redistributed or modified. Versions published before 2026-10-11 were released under the Apache License 2.0 and remain available under it.

Oxid and the Oxid logo are trademarks of Optersoft, S.L. The licence grants no rights to the name or the logo. You may say that your work uses or is built with Oxid.

Oxid's reactive model follows Solid and Leptos.

Metadata

Release files for oxid 0.27.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for oxid 0.27.1
File Size Uploaded Provenance
oxid-0.27.1.tar.gz 3.1 MB Without provenance Details

Built distributions (wheels)

Table of built distributions (wheels) for oxid 0.27.1
File
oxid-0.27.1-py3-none-win_amd64.whl Python 3 none Windows x86-64 Details
oxid-0.27.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl Python 3 none Linux glibc 2.17+ x86-64 Details
oxid-0.27.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl Python 3 none Linux glibc 2.17+ ARM64 Details
oxid-0.27.1-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details
oxid-0.27.1-py3-none-macosx_10_12_x86_64.whl Python 3 none macOS 10.12+ x86-64 Details
oxid-0.27.1-py3-none-any.whl Python 3 none any Details

Total release size: 260.1 MB

Release files / oxid-0.27.1.tar.gz

Download URL oxid-0.27.1.tar.gz
Size 3.1 MB
Tags Source
SHA-256 checksum
How to use checksums
aee8833e60c5966bf73e14be4825e4a7edb0a71d2d8b23c565c4975bbf97b52b
BLAKE2b-256 checksum
How to use checksums
a33693a5bfb9b1c7e44652f099f619338f5bec643ca878c71ae948bdd6710761
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / oxid-0.27.1-py3-none-win_amd64.whl

Download URL oxid-0.27.1-py3-none-win_amd64.whl
Size 55.7 MB
Tags Python 3 Windows x86-64
SHA-256 checksum
How to use checksums
25db8b5f1ed30ee51ab581bae4fc6c10851bc9c7329865a2060dcdf0ebec0227
BLAKE2b-256 checksum
How to use checksums
768a8e5049b757d9155749a5a08f07e8498f4a5f1618c026bb8bf68815218a65
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / oxid-0.27.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL oxid-0.27.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 50.1 MB
Tags Linux glibc 2.17+ x86-64 Python 3
SHA-256 checksum
How to use checksums
73e01224bc61baa94afba126d9d61d5b6b38461ff7e1a5b5d04f27752a06b168
BLAKE2b-256 checksum
How to use checksums
ff2e10db77a56f7fa8865476ffef2fb247bfd07d11aaea977b16bcc7eeb677b1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / oxid-0.27.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL oxid-0.27.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 48.1 MB
Tags Linux glibc 2.17+ ARM64 Python 3
SHA-256 checksum
How to use checksums
74955af163b584dec709fd1ddb9aab033174238ba0added4258e750dab9f203f
BLAKE2b-256 checksum
How to use checksums
45aef17cde03de49e3169a8b283c745d17cb11766e88974fbd1c60389483e651
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / oxid-0.27.1-py3-none-macosx_11_0_arm64.whl

Download URL oxid-0.27.1-py3-none-macosx_11_0_arm64.whl
Size 49.6 MB
Tags Python 3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
924eb71097cc7a4dfaedfb80d426a9fc38a4a8faea55d2a9ff7307a8a1f0985e
BLAKE2b-256 checksum
How to use checksums
3ec7714f1b678a16ec2d127c643345bd614061043c2b16fc04b383129ec311a4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / oxid-0.27.1-py3-none-macosx_10_12_x86_64.whl

Download URL oxid-0.27.1-py3-none-macosx_10_12_x86_64.whl
Size 51.6 MB
Tags Python 3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
07d9e97e9e925b1a7bea2a75bff40734507342fab77f9c9367aa6d875f8258f2
BLAKE2b-256 checksum
How to use checksums
14fef662d930ec206549b483659cd4f57be0f2936ac8fcdd7801a2ac216875e5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / oxid-0.27.1-py3-none-any.whl

Download URL oxid-0.27.1-py3-none-any.whl
Size 2.0 MB
Tags Python 3
SHA-256 checksum
How to use checksums
a3ad336ffc6c48d9c7a3beb255314015ed05795907858fb01f1091eb87e1064c
BLAKE2b-256 checksum
How to use checksums
f7df6ac449f468411a01c7d0a207100b68b73a91486ce3b28f7d7f0bd7ea6284
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for oxid 0.27.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page