An experimental lightweight language-agnostic cross-platform UI engine — Check out the docs!
StdUI is a lightweight cross-platform UI engine that can be used with any programming language. It's designed to be spawned as process and communicate with the main application via stdin/stdout. This allows for easy integration with various programming languages and frameworks. It supports a reduced sub-set of HTML/CSS for UI rendering. No browser is involved.
The goal is to provide a simple way to build native desktop applications with similar layout and style options to what you would expect from the web, but without the overhead of a full browser engine. It should also enable languages where UI libraries are rare. Its target use case is smaller applications with low to medium complexity that need a simple and easy UI.
Warning
This project is experimental. Expect bugs, missing features and breaking changes. The API is not stable and may change without deprecation. Use at your own risk.
- Cross-platform (Windows, macOS, Linux)
- Language agnostic (communicates via stdin/stdout)
- Supports a reduced sub-set of HTML/CSS for UI layouting and styling
- Pane-based layout system
- Interactive widgets:
- Buttons
- Text/Number/Password inputs
- Checkboxes
- Sliders
- Progress bars
- Color Picker
- Display of local images via
imgtag - Native file/folder dialogs
- File drop support
- Supports playing of sounds
- Clipboard text access
- Notification toasts
- ~5MB binary size
- ...
- Simple: Minimal example just showing the StdUI logo and a button
- Interactive Elements: Showcases all the built-in interactive widgets
- Todo List: Simple todo list app
- Chat: Simple chat interface with message input and display (no real networking, just simulates a conversation)
- IRC Client: A working, but simple IRC client
All these examples are based on the Go SDK.
This uses litehtml for html/css layouting. This means that only a reduced sub-set of HTML/CSS is supported and there are quirks in the way some html/css is interpreted. For example, the display: flex property is suported, but gap is not. Sometimes you might need to fall back to table layout to achieve the desired result or use some other slightly strange workarounds.
stdui is a compiled C++ binary that opens a native OS window and renders HTML with interactive widgets. Your application controls it by spawning it as a child process and exchanging newline-delimited JSON over stdin/stdout. Any language that can start a process and read/write pipes can drive it.
Your App (Go, Python, anything)
│ stdin → JSON commands
│ stdout ← JSON events
▼
stdui binary (C++)
┌-----------------------------─┐
│ litehtml — HTML/CSS layout │
│ ImGui — interactive │
│ widgets │
│ raylib — window, audio │
└------------------------------┘
The binary blocks on startup waiting for a settings message. Send it immediately after spawning the process, before doing anything else:
{ "action": "settings", "data": { "title": "My App", "windowWidth": 800, "windowHeight": 600 } }Once the window is open and ready, the binary replies:
{ "action": "ready" }Only send content after you receive ready.
Push HTML to the window with update-content. The binary re-renders immediately:
{ "action": "update-content", "data": "<h1>Hello from stdui</h1>" }Whenever your state changes, build a new HTML string and send another update-content. There is no incremental patching — just replace the whole thing.
Standard HTML/CSS handles layout and styling. For interactive elements, use the built-in <ui-*> tags:
<ui-button text="Save" action="save"></ui-button>
<ui-input id="name" type="text" placeholder="Your name" value=""></ui-input>
<ui-select id="theme" options="Light|Dark|System" value="Light"></ui-select>
<ui-slider id="vol" min="0" max="1" value="0.5"></ui-slider>
<ui-checkbox id="notify" label="Enable notifications" checked="false"></ui-checkbox>When the user interacts with one of these, the binary emits an event on stdout.
A button click:
{ "action": "button-clicked", "data": { "action": "save", "text": "Save", "pane": "main" } }An input or widget value change:
{ "action": "input-changed", "data": { "id": "name", "value": "Alice", "pane": "main" } }A link click (<a href="...">):
{ "action": "url-clicked", "data": { "url": "my-app://some-route", "pane": "main" } }Window closed by the user:
{ "action": "window-closed" }Read these lines from stdout in a loop, parse the JSON, and react however your app needs to.
You → stdui {"action":"settings","data":{"title":"Hello","windowWidth":400,"windowHeight":300}}
stdui → You {"action":"ready"}
You → stdui {"action":"update-content","data":"<h1>Hi</h1><ui-button text=\"Click me\" action=\"hi\"></ui-button>"}
... user clicks the button ...
stdui → You {"action":"button-clicked","data":{"action":"hi","text":"Click me","pane":"main"}}
You → stdui {"action":"update-content","data":"<h1>You clicked it!</h1>"}
... user closes the window ...
stdui → You {"action":"window-closed"}
That's the entire model. Every interaction follows this same pattern: send action, receive events, send new action.
You can learn more about the protocol in the protocol specification.
A Go SDK is available in the sdk/go directory. It provides a simple API for communicating with the StdUI process and reacting to user interactions. Usage examples can be found in the ./example folder. To install the Go SDK, run:
go get github.com/BigJk/stdui/sdk/go@latest
package main
import (
"fmt"
"os"
stdui "github.com/BigJk/stdui/sdk/go"
)
func main() {
// If you don't want to download the binary yourself, you can use EnsureBinary to download the latest release
// from GitHub and place it in the working directory.
if err := stdui.EnsureBinary("", ""); err != nil {
panic(err)
}
client, err := stdui.Start("./stdui", stdui.Settings{
Title: "Simple",
WindowWidth: stdui.Ptr(700),
WindowHeight: stdui.Ptr(400),
})
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to start stdui: %v\n", err)
os.Exit(1)
}
client.OnReady(func() {
client.UpdateContent("Hello World") //nolint:errcheck
})
client.OnLog(func(namespace, message string) {
fmt.Fprintf(os.Stderr, "[log] %s: %s\n", namespace, message)
})
client.OnError(func(err error) {
fmt.Fprintf(os.Stderr, "[error] %v\n", err)
})
client.Wait()
}This project is built on top of several excellent libraries:
- raylib - Simple and easy-to-use library for video games and graphics
- litehtml - Lightweight HTML/CSS layout rendering engine
- ImGui - Immediate mode GUI library for interactive widgets
- ImHTML - ImGui extension for rendering HTML
- hjson - Human-friendly JSON format parser
- Native File Dialog - Cross-platform file/folder dialog library



