Skip to main content

Mxlit

Mxlit is a lightweight, pure-Python alternative to Streamlit, powered by FastAPI and HTMX. It allows you to build interactive web applications by writing simple top-to-bottom Python scripts, without having to write any HTML, CSS, or JavaScript.

Features

  • No Frontend Code Required — Write only Python; zero HTML, CSS, or JavaScript.
  • Fast and Lightweight — HTMX partial-page reloads instead of heavy React bundles or WebSockets.
  • State Management — Built-in session_state persists data across interactions, just like Streamlit.
  • Rich Components — Text, markdown, dataframes, metrics, images, charts, and 11+ interactive widgets.
  • Layouts — Sidebars, columns, tabs, expanders, and horizontal containers.
  • Advanced UI — Named Entity Recognition (NER) highlighting and streaming text (write_stream) built in.
  • Python-Driven Styling — Pass Tailwind utility classes to any component via className="..." directly from Python code.
  • Live Theme Generatormt.theme(dict) stores the palette in session_state; the framework injects CSS variables automatically on every response with no manual code needed.
  • oat.ink UI Library — Semantic, zero-dependency component styles; buttons, alerts, tabs, and forms styled automatically.
  • Tailwind CSS v4 — Utility classes compiled to a single style.css; no Node.js required (uses pytailwindcss).

Tech Stack

Layer Technology Version Role
Language Python 3.12+ Application logic and component API
Web framework FastAPI 0.111+ HTTP server, routing, SSE broadcast
ASGI server Uvicorn 0.29+ Production-grade async server
Templating Jinja2 3.1+ Server-side HTML rendering
Interactivity HTMX 2.x Partial page reloads, SSE event handling
UI library oat.ink latest Semantic, zero-dependency component styles (vendored)
CSS processor Tailwind CSS v4 Utility-first CSS — no Node.js needed
CSS CLI pytailwindcss latest pip-installable Tailwind v4 standalone binary
Charts Chart.js latest Line, bar, area, and scatter charts
Data pandas 2.x DataFrame and table rendering

Installation

Option 1 — pip (recommended)

pip install mxlit

Option 2 — from source using setup.py

Clone the repository and install in editable mode so any local changes are reflected immediately:

git clone https://github.com/kalpi-4/mxlit.git
cd mxlit
pip install -e .

Or install as a regular (non-editable) package from source:

pip install .

Option 3 — with pinned dependencies (requirements.txt)

To reproduce the exact dependency versions used during development:

git clone https://github.com/kalpi-4/mxlit.git
cd mxlit
pip install -r requirements.txt
pip install -e .

Option 4 — uv (fast, reproducible via uv.lock)

Clone the repository and let uv create the virtualenv and install everything from the lockfile, including the dev extra (pytailwindcss, needed for the automatic CSS rebuild on mxlit run):

git clone https://github.com/kalpi-4/mxlit.git
cd mxlit
uv sync --extra dev

On a network with a TLS-intercepting proxy, add --native-tls (or set UV_NATIVE_TLS=true) if uv sync fails with a certificate error.

Quick Start

Create a file named app.py:

import mxlit as mt

mt.title("Hello Mxlit!")
mt.write("This is a simple interactive application.")

if "counter" not in mt.session_state:
    mt.session_state["counter"] = 0

if mt.button("Click me!"):
    mt.session_state["counter"] += 1

mt.write("Button clicked:", mt.session_state["counter"], "times")

Run your application using the Mxlit CLI:

mxlit run app.py

By default, the server will start at http://127.0.0.1:8501.

You can specify a custom host or port:

mxlit run app.py --host 0.0.0.0 --port 8000

If you installed with uv (Option 4), run through uv run instead so it uses the project's managed virtualenv without activating it:

uv run mxlit run app.py

Try one of the bundled samples the same way:

uv run mxlit run .\samples\kitchen_sink.py

How It Works

Mxlit executes your Python script top-to-bottom on every interaction. Behind the scenes:

  1. The mxlit CLI spins up a FastAPI server.
  2. The UI is dynamically generated as an HTML tree using Jinja2 templates.
  3. User interactions (clicks, text input, slider changes) trigger HTMX POST requests to the server.
  4. The server updates session_state, re-runs the Python script, and responds with only the HTML fragments that need to be updated.

External API Interactions

Because Mxlit applications are powered by a standard FastAPI server, you can directly interact with the application's global state from external sources using simple HTTP POST requests.

The /interact API

The /interact POST endpoint is typically used by the HTMX frontend to submit user interactions. However, you can call it manually (e.g., via webhooks or external scripts) by sending URL-encoded form data. The server updates the session_state, re-runs the Python script, and responds with the newly rendered HTML fragment.

The /modify API (Real-time Broadcasts)

The /modify POST endpoint allows external systems or background workers to update the app's state and instantly push the updated UI to all currently connected browsers in real-time using Server-Sent Events (SSE). It returns a JSON status response, making it perfect for headless automation or IoT integrations.

curl -X POST http://127.0.0.1:8501/modify \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "counter=100"

Advanced Usage

Mxlit supports many of the patterns you know and love:

import mxlit as mt
import pandas as pd

# Layouts
with mt.sidebar:
    mt.header("Settings")
    rows = mt.slider("Rows to show", 1, 10, 5)

col1, col2 = mt.columns(2)
with col1:
    mt.metric("Users", 1024, "+12%")

# Dataframes
df = pd.DataFrame({"A": range(rows), "B": range(rows)})
mt.dataframe(df)

# Text Streaming
def my_stream():
    for word in ["Streaming ", "text ", "is ", "cool!"]:
        yield word
mt.write_stream(my_stream())

Grid, rows, and columns

For finer control than mt.columns(), use OAT's 12-column grid directly — mt.grid() holds one or more mt.row()s, each split into mt.col()s by span (1-12), with optional offset and end alignment:

import mxlit as mt

with mt.grid():
    with mt.row():
        with mt.col(6):
            mt.write("Left half")
        with mt.col(6):
            mt.write("Right half")

    with mt.row():
        with mt.col(4):
            mt.write("One third")
        with mt.col(4, offset=4):
            mt.write("Last third, shifted right")

    with mt.row():
        with mt.col(3, end=True):
            mt.metric("Uptime", "99.98%")

Sidebar

mt.sidebar works as a bare context manager, a callable that accepts className, or — matching OAT's <aside data-sidebar> recipe — as a context manager yielding named .header / .footer slots, with any un-slotted content falling into the middle <nav> region:

import mxlit as mt

with mt.sidebar as s:
    with s.header:
        mt.write("Logo")
    mt.write("Nav link 1")   # un-slotted -> <nav>
    mt.write("Nav link 2")
    with s.footer:
        mt.button("Logout")

# or, styled and without slots:
with mt.sidebar(className="w-64"):
    mt.title("Nav")

Customizing the theme

The theme is driven by src/mxlit/constants/theme.json — a Material Theme Builder export. It contains 386 hex tokens organised by scheme and palette:

{
  "schemes": {
    "light": {
      "primary":      "#415F91",
      "secondary":    "#565F71",
      "background":   "#F9F9FF",
      "onBackground": "#191C20"
    }
  },
  "palettes": {
    "primary": { "40": "#415F91", "80": "#AAC7FF" }
  }
}

Call mt.theme() to apply overrides at runtime. Keys are dot-notation paths into the JSON; CSS variables update automatically on the next response — no page reload needed.

import mxlit as mt

# returns the full resolved dict (all 386 tokens)
_t = mt.theme()

# override any token
mt.theme({"schemes.light.primary": "#9333ea"})

# live color pickers — key follows "theme_" + path.replace(".", "_")
with mt.sidebar:
    mt.color_picker("Primary", value=_t["schemes.light.primary"],
                    key="theme_schemes_light_primary")
    mt.color_picker("Background", value=_t["schemes.light.background"],
                    key="theme_schemes_light_background")

See samples/class_styling_demo.py for a full working example.


Passing utility classes from Python

Every mxlit component accepts an optional className keyword argument. The value is a space-separated string of Tailwind utility classes applied directly to the component's primary HTML element.

Full working demosamples/class_styling_demo.py showcases every component type with className applied, plus the live theme generator. Run it with python -m mxlit run samples/class_styling_demo.py.

import mxlit as mt

# Typography — colour, size, weight
mt.title("Sales Dashboard",   className="text-3xl font-extrabold")
mt.write("Last updated: now", className="text-sm text-slate-400 italic")

# Badges — background and text colour via Tailwind
mt.badge("stable",     className="bg-green-100 text-green-800 font-semibold")
mt.badge("deprecated", className="bg-red-100   text-red-700   line-through")

# Full-width button
if mt.button("Save changes", className="w-full"):
    mt.success("Saved!", className="mt-1")

# Constrained form inputs
mt.text_input("Full name", className="max-w-sm")
mt.selectbox("Language", ["Python", "Rust", "Go"], className="max-w-xs")

# Chart with a card border
mt.bar_chart({"A": 10, "B": 20, "C": 15}, className="rounded-lg border p-2")

# Metric cards in a row
col1, col2 = mt.columns(2)
with col1: mt.metric("Revenue", "$84,200", "+12%", className="w-full")
with col2: mt.metric("Users",   "3,412",   "+5%",  className="w-full")

# Styled expander and pill container
with mt.expander("Model details", className="border rounded-lg"):
    mt.write("GPT-4o-mini · 128k context", className="text-sm")

with mt.container(horizontal=True, className="gap-3 flex-wrap p-4 bg-slate-50 rounded-xl border"):
    mt.badge("Python 3.12",  className="bg-blue-100  text-blue-800  text-sm px-3 py-1")
    mt.badge("Tailwind v4",  className="bg-sky-100   text-sky-800   text-sm px-3 py-1")

Note: error/warning/info/success (in status.py) also accept a legacy class_ kwarg as a compat alias for className. Every other component only accepts className.

Where the class lands per component type

Component Element that receives className
title / header / subheader <h1> / <h2> / <h3>
write / text <p>
markdown / html <div>
code / json <pre>
latex <div class="mx-latex …">
badge <span class="badge …">
error / warning / info / success <div role="alert" …>
button <button>
text_input / checkbox / slider / toggle / number_input / text_area / date_input / color_picker <label> wrapper
radio <fieldset>
selectbox <div data-field> wrapper
dataframe / table <div class="table …">
metric <div class="mx-metric …">
image / logo <img>
audio <audio>
video <video>
line_chart / bar_chart / area_chart / scatter_chart <div class="mx-chart …">
expander <details>
container <div>
ner_text outer <div class="mx-ner …">
write_stream <div class="mx-stream …">

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mxlit-0.1.3.tar.gz (198.7 kB view details)

Uploaded Source

Built Distribution

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

mxlit-0.1.3-py3-none-any.whl (96.8 kB view details)

Uploaded Python 3

File details

Details for the file mxlit-0.1.3.tar.gz.

File metadata

  • Download URL: mxlit-0.1.3.tar.gz
  • Upload date:
  • Size: 198.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for mxlit-0.1.3.tar.gz
Algorithm Hash digest
SHA256 e914291bde7e0942ff24871bede49350adc1d31ac908c0727ca89bf5fe2fba58
MD5 a4fbab38a70161f16116ea5c9959accf
BLAKE2b-256 a8543479c82e7dd69d3741ec1a41f2316372b383c02b8714a24fd4437c74e64d

See more details on using hashes here.

File details

Details for the file mxlit-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: mxlit-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 96.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for mxlit-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 232de526dc1b5d0f1b613a8b4432b55db5ace59631a942542a1b4df8aeaa230f
MD5 cd88a7ef5d0c6d5d3dd02f730dd9c281
BLAKE2b-256 bbccdb8e0612f0b93ad3d00c86b4ae9a584a280506f6ea6addaaf780c18c618d

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.3 This release

2 files

0.1.2

2 files

Supported by

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