Research: Philosophy
Design philosophy and the interactivity spectrum.
The original vision: no CSS, no JavaScript
lofigui started from a simple premise: what if a web UI was just print() statements rendered as plain HTML? No CSS framework, no JavaScript, no build step. The reasons:
-
Simplicity — Every dependency is a thing to learn, update, and debug. Plain HTML is the lowest common denominator. A developer who can write
print("hello")can build a UI. -
Deployment — A single binary (Go) or a minimal Python package that serves HTML over HTTP. No node_modules, no bundler, no static asset pipeline. Copy the binary to a server and run it. This matters especially for gokrazy deployments and internal tools where infrastructure is minimal.
-
Understandability — "View Source" shows exactly what the server sent. There is no client-side rendering, no virtual DOM diffing, no hydration step. The browser does what browsers were built to do: render HTML.
The Bulma compromise
Plain HTML is functional but ugly. For internal tools used daily, aesthetics matter enough to justify a CSS framework. Bulma was chosen because:
- It is CSS-only — no JavaScript runtime
- It is a single CDN link — no build step
- It makes tables, forms, and layout look professional with class names alone
This is the first trade-off: we accepted a CDN dependency for better-looking output. The framework still works without Bulma (plain HTML renders fine), but the examples and defaults assume it.
godocs originally started with very simple hand-written CSS. Over time it grew complex and inconsistent — and it was actually more CSS and more complexity than switching to Bulma for a more consistent result. Making a custom CSS resulted in much more complexity and more CSS than restricting to a standard. The same principle may apply to charts and other areas: a focused, well-chosen dependency can be simpler than a DIY approach that accumulates complexity over time.
Removing JavaScript: precedent and practice
The UK Government Digital Service removed jQuery from GOV.UK in 2022 — a site serving millions of users. Their reasoning: fewer bytes, fewer failure modes, better accessibility. If GOV.UK can serve a nation without jQuery, an internal tool can certainly manage without React.
lofigui takes this further. The base framework uses zero JavaScript. The browser's native capabilities — HTML rendering, form submission, HTTP Refresh — handle everything in examples 01-08.
Where JavaScript creeps back in
Two features introduce JavaScript, both deliberately:
WASM (examples 03, 04, 07, 08) — Go compiled to WebAssembly requires a small JS loader (wasm_exec.js). This is the price of running the same Go code in the browser without a server. The JS is boilerplate glue, not application logic.
HTMX (examples 09, 10) — A single <script> tag that adds hx-get and hx-trigger attributes to HTML elements. HTMX exists because full-page HTTP Refresh polling has a real usability problem: if you are trying to enter information in a form or click a button, the page refresh interrupts you. The input loses focus, the form resets, the click never registers. For display-only dashboards, polling is fine. For anything interactive, it is maddening.
HTMX solves this by updating only the parts of the page that change, leaving forms and buttons untouched. It is the minimum JavaScript needed to make multi-page dynamic sites usable.
The JavaScript budget
The position is not "no JavaScript ever" but "justify every byte":
| Layer | JS? | Justification |
|---|---|---|
| Base (examples 01-08) | None | Full-page refresh is sufficient |
| HTMX (examples 09-10) | ~14KB | Partial updates make interactive pages usable |
| WASM (examples 03-04) | ~16KB loader | Enables server-free deployment |
No bundler, no npm, no build step. Each JS dependency is a single file loaded from a CDN or embedded.
The interactivity spectrum
Web applications sit on a spectrum of interactivity. lofigui deliberately targets the lower end, where simplicity wins. Understanding the spectrum helps choose the right approach for a given project.
| Level | Approach | lofigui support | JS required | Examples |
|---|---|---|---|---|
| 1 | Static sites | Docs only | None | — |
| 2 | Static + forms | Full (CRUD pattern) | None | 06 (Notes CRUD) |
| 3 | Polling (whole page) | Full (App + Refresh) | None | 01 (Hello World), 02 (SVG Graph), 07 (Water Tank), 08 (Multi-Page) |
| 4 | HTMX (partial updates) | Full (Controller + HTMX) | ~14KB | 09 (Water Tank HTMX), 10 (Maintenance), 12 (Batch Yield) |
| 5 | SPA (full Ajax) | Out of scope | Framework | — |
Most internal tools and dashboards live at levels 2-4. lofigui covers that range with a print()-based API and zero-to-minimal JavaScript.
1. Static sites
Pure HTML + CSS. No server, no forms, no JavaScript. Documentation, landing pages, blogs. Generated once, served from a CDN or file server.
Examples: GitHub Pages, statichost.eu docs, this documentation site.
2. Static + static forms
HTML + CSS + traditional HTML form submissions. The form POSTs to the server, the server processes it and returns a new page. Each interaction is a full page load. No JavaScript needed — the browser handles form encoding and submission natively.
lofigui examples: 06 (Notes CRUD) — form POST, redirect, re-render.
3. Refreshing whole page (polling)
The server renders the complete page. The browser periodically reloads it via <meta http-equiv="Refresh">. Good for dashboards and status pages where the user watches but doesn't interact. The entire page is replaced on each refresh cycle.
Limitation: you cannot interact with the page while it refreshes. Clicking a button, filling in a form field, or selecting a dropdown — all are interrupted by the next refresh. This is fine for display-only views but unusable for anything requiring user input during live updates.
lofigui examples: 01 (Hello World polling), 07-08 (Water Tank dashboards).
4. HTMX partial updates (dynamic pages)
Only parts of the page update — the rest stays stable. Forms, text inputs, and buttons remain functional while live data refreshes around them. HTMX makes this possible with hx-get and hx-trigger attributes — the server still renders HTML, but the browser swaps only the targeted <div>.
This is the sweet spot for lofigui: server-rendered HTML with just enough client-side behaviour to make forms and controls usable alongside live data. A text box on a dynamic form — impossible with full-page polling — works naturally with HTMX partial updates.
lofigui examples: 09 (Water Tank HTMX), 10 (Maintenance with progress).
5. Fully interactive single-page apps (SPA)
Full client-side rendering with Ajax/fetch. React, Vue, Svelte territory. The server becomes a JSON API; the browser builds the entire UI. Maximum interactivity, maximum complexity: bundlers, virtual DOM, state management, hydration.
lofigui does not target this level. If your project needs a full SPA, use a proper SPA framework. lofigui's value is avoiding that complexity for the many tools that don't need it.
Print as interface
The fundamental insight is that print() is the most natural programming interface. Every developer learns it first. lofigui preserves that — you print things, they appear on a web page. The abstraction cost is near zero.
Progressive complexity
The examples are ordered deliberately:
- Print and poll (01) — the simplest useful pattern
- Synchronous render (02) — when you don't need async
- WASM (03, 04) — same code, no server
- CRUD (06) — forms and state
- Real-time dashboards (07-09) — SVG, multi-page, HTMX
- Background operations (10) — goroutines, cancellation, progress
Each step adds one concept. You stop at the level of complexity your project needs.
Where does lofigui sit?
lofigui is for single-process, small-audience tools. The sweet spot: 1-10 users, one real object (a machine, a simulation, a long-running process) with a few pages showing different views of it. It is not competing with React or even Streamlit — it is competing with "I'll just use the terminal" or "I'll write a quick bash CGI script".