Skip to main content

Config-driven, zero-dependency web GUI framework with dockable panes

Project description

mkui

A config-driven, dependency-free web GUI framework with a floating-frame workspace and dockable panes inside each frame. Designed to pair with mkio — the same project's TOML-driven microservice backend — but works against any backend (or none at all).

Model

mkui-app
├── mkui-menubar
├── mkui-workspace
│   ├── mkui-frame  ← floating, movable, resizable. Clamped to workspace.
│   │   └── layout tree
│   │       └── TabGroup
│   │           ├── tab bar
│   │           └── mkui-pane  ← leaf content host
│   ├── mkui-frame
│   │   └── split (h | v)
│   │       ├── TabGroup → panes
│   │       └── TabGroup → panes
│   └── ...
└── mkui-statusbar

Top-level windows are frames — floating chrome with titlebar and 8-way resize. Frames don't dock into each other. Inside each frame lives an independent, normalized layout tree of splits, tab groups, and panes. Docking — splitting, tabbing, tearing out — happens entirely inside frames.

Design commitments (things meant to hold up indefinitely):

  • Structural invariant: every pane leaf sits inside a tab group. A single-pane frame is a single-tab group. This removes a whole class of special cases from the renderer and the drop logic.
  • Proportional resize by construction: frame positions are fractions of the workspace rect, and split ratios sum to 1. Resizing the browser rescales every frame, every split, and every pane with no extra code.
  • Viewport clamping invariant: every frame move/resize passes through a single clampToDock helper. Frames cannot escape the workspace — shrinking the viewport drags stragglers back in.
  • Stable pane identity: pane elements live in a workspace-owned pool and are re-parented via appendChild when re-docked. Content state, subscriptions, scroll position, and DOM focus all survive.
  • Zero runtime dependencies. Web Components, so it drops into React / Vue / Svelte / vanilla identically.

Interactions

  • Frame titlebar → drag to move (clamped). Frame edges/corners → 8-way resize (clamped, min 160×80).
  • Frame close button → closes the frame; panes inside are parked in the pool (state preserved) and can be brought back by code.
  • Tab click → switch active pane in that tab group.
  • Tab drag → if the cursor leaves the tab bar by more than a few pixels, the pane is torn out into a new frame at the cursor.
  • Dragging a torn-out (or any single-pane) frame over another frame shows drop zones: edges split, center adds as a tab. Release to dock.
  • Splitter drag → resize the ratio between two children of a split.
  • Any mousedown inside a frame raises it to the top of the z-order.

Configs

mkui's runtime input is JSON. When the backend is mkio, mkio's Python side parses app.toml with stdlib tomllib and serves the result as JSON — so the browser never needs a TOML parser. For other backends, author or generate app.json directly.

Minimal config:

{
  "app":     { "title": "Trading desk", "theme": "dark" },
  "menubar": [{ "label": "File", "items": [{ "label": "Quit", "action": "app.quit" }] }],
  "statusbar": { "left": [{ "type": "text", "bind": "status.message" }] },

  "panes": {
    "orders":    { "title": "Orders", "type": "mkio-table", "service": "all_orders" },
    "chart":     { "title": "Chart",  "widgets": [{ "type": "text", "text": "Chart goes here" }] },
    "inspector": { "title": "Inspector", "widgets": [{ "type": "text", "text": "Properties" }] }
  },

  "frames": [
    {
      "id": "main",
      "x": 0.05, "y": 0.05, "w": 0.65, "h": 0.9,
      "layout": { "type": "tabs", "active": 0, "children": ["orders", "chart"] }
    },
    {
      "id": "aux",
      "x": 0.72, "y": 0.05, "w": 0.23, "h": 0.9,
      "layout": { "type": "tabs", "children": ["inspector"] }
    }
  ],

  "mkio": { "url": "ws://localhost:8080/ws" }
}

Frame positions (x, y, w, h) are fractions of the workspace rect.

Standalone mode

<!doctype html>
<link rel="stylesheet" href="/mkui/styles/mkui.css">
<script type="module" src="/mkui/src/index.js"></script>
<mkui-app config="/mkui/config.json"></mkui-app>

Library mode

import { registerPaneType } from "mkui";
import "mkui";   // side-effect: registers custom elements

registerPaneType("clock", (spec, app, host) => {
  const el = document.createElement("div");
  host.appendChild(el);
  setInterval(() => { el.textContent = new Date().toLocaleTimeString(); }, 1000);
});

const root = document.querySelector("mkui-app");
await customElements.whenDefined("mkui-app");
root.setConfig({
  panes:  { clock: { title: "Clock", type: "clock" } },
  frames: [{ id: "f1", x: 0.3, y: 0.3, w: 0.3, h: 0.3,
             layout: { type: "tabs", children: ["clock"] } }],
});

// Add more frames at runtime:
root.workspace.addFrame({ x: 0.5, y: 0.1, w: 0.4, h: 0.4,
                          layout: { type: "tabs", children: ["other-pane"] } });

Built-in widgets and pane types (v1)

  • Widgets (lightweight content inside a pane or statusbar slot):
    • text — static or bind-ed to a state path
    • button — fires an action by name
  • Pane types (whole-pane custom rendering):
    • mkio-table — subscribes to an mkio subpub service and renders rows
  • Custom pane types are the primary extensibility surface. Register with registerPaneType(name, factory); reference from config as type = "<name>".

Installation

pip install mkui

Then serve the static assets from your Python backend:

import mkui

# With mkio (toml config):
#   [static]
#   "/mkui" = "<result of mkui.static_dir>"

# With FastAPI / Starlette:
from starlette.staticfiles import StaticFiles
app.mount("/mkui", StaticFiles(directory=mkui.static_dir))

Running the examples

cd mkui/static
python3 -m http.server 8000
# http://localhost:8000/examples/standalone-json/
# http://localhost:8000/examples/library-js/

Project layout

mkui/                    Python package (pip install mkui)
  __init__.py            Exposes static_dir path
  static/
    src/
      core.js            State store, registries, App class
      index.js           Side-effect entry point
      layout/
        tree.js          Normalized tree math
        drag.js          clampToDock, snap, dropZoneFor, frac↔rect
      components/
        app.js           <mkui-app> — the shell
        menubar.js       <mkui-menubar>
        statusbar.js     <mkui-statusbar>
        workspace.js     <mkui-workspace> — frame list, arrangement, snap
        frame.js         <mkui-frame> + <mkui-pane>
      widgets/
        text.js  button.js  mkio-table.js
      mkio-bridge.js     Lazy-loads mkio's /mkio.js client
    styles/mkui.css      Default theme (CSS custom properties)
    examples/
      standalone-json/   Loaded from a static config
      library-js/        Built imperatively from JS
pyproject.toml           Python build config
package.json             JS dev tooling
tests/layout.test.js     33 unit tests

Project details


Download files

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

Source Distribution

mkui-0.1.0.tar.gz (31.9 kB view details)

Uploaded Source

Built Distribution

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

mkui-0.1.0-py3-none-any.whl (34.6 kB view details)

Uploaded Python 3

File details

Details for the file mkui-0.1.0.tar.gz.

File metadata

  • Download URL: mkui-0.1.0.tar.gz
  • Upload date:
  • Size: 31.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for mkui-0.1.0.tar.gz
Algorithm Hash digest
SHA256 64e7d75b2404c25cef9bdc981e07e1c44108b1689ed0b8c161b30c6f01064819
MD5 6b0a17abfe6f32a409845959c25d5033
BLAKE2b-256 ded7f4d787db4ee0e116b2baaf55fff8d5b90a35f38b7c7ddb45c8c91c9b8db9

See more details on using hashes here.

File details

Details for the file mkui-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: mkui-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 34.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for mkui-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 136b764b5c255739639f9073f96d15b5396b3613d4df1ce1d1bc0662f7458ae2
MD5 556a0f58c0e1eadad88cab9b6a77991e
BLAKE2b-256 ba1cd927a4aaeea84c22516ae23eeebb825cdb3e1f3237d29585d9f4ff3037fa

See more details on using hashes here.

Supported by

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