Build cross-platform desktop apps with V + HTML/CSS/JS
Features β’ Quick Start β’ Examples β’ Documentation β’ Contributing
| Gallery Demo | Element Plus |
![]() |
![]() |
| Real-time Charts | Todo App |
![]() |
![]() |
| File Upload | |
![]() |
- β‘ WebSocket-Powered β Real-time bidirectional communication without HTTP overhead
- π¨ Use Your Browser β Leverage modern web technologies for beautiful UIs
- π Secure by Default β Token-based authentication, XSS protection, and path traversal prevention
- π Cross-Platform β Linux, macOS, and Windows support with auto browser detection
- π¦ Lightweight β Pure V implementation, no external dependencies
- π― htmx Integration β Seamless integration with official htmx (no modifications required)
- π§ Backend-to-Frontend β Execute JavaScript from backend with
run_js() - π₯ Multi-Client Support β Optional support for multiple browser clients
- π Single Executable β Embed frontend files into binary for easy distribution
- Introduction
- Motivation
- Installation
- Quick Start
- Features
- Architecture
- Examples
- Security
- Browser Support
- Contributing
- License
vxui is a lightweight, cross-platform desktop UI framework that uses your browser as the display and V as the backend. Unlike traditional web frameworks, vxui:
- No HTTP/HTTPS server β Direct WebSocket communication
- No build step β Just V code and HTML files
- No framework lock-in β Use any frontend libraries you like
vxui = browser + htmx + websocket + V
- Every desktop has a browser β Modern browsers offer better rendering than native GUI toolkits
- WebSocket > HTTP β Why use a web server for desktop apps? WebSocket enables true bidirectional communication
- Full-stack V β Write your entire app in one language
- V (v0.4.0 or later)
- Chrome, Chromium, Edge, or Firefox
v install --git https://github.com/kbkpbot/vxui.gitgit clone https://github.com/kbkpbot/vxui.git ~/.vmodules/vxuimodule main
import vxui
import x.json2
struct App {
vxui.Context
mut:
counter int
}
@['/clicked']
fn (mut app App) clicked(message map[string]json2.Any) string {
app.counter++
return '<div id="counter">Count: ${app.counter}</div>'
}
fn main() {
mut app := App{}
app.logger.set_level(.debug)
vxui.run(mut app, './ui/index.html') or {
eprintln('Error: ${err}')
exit(1)
}
}<!DOCTYPE html>
<html>
<head>
<script src="./js/htmx.js"></script>
<script src="./js/vxui-ws.js"></script>
</head>
<body>
<h1>Hello vxui!</h1>
<button hx-post="/clicked" hx-swap="outerHTML">
Click Me
</button>
<div id="counter">Count: 0</div>
</body>
</html>v run main.vflowchart TB
subgraph Frontend["Browser"]
HTML["HTML/CSS"]
HTMX["htmx attributes"]
WS["vxui-ws.js"]
end
subgraph Backend["V Backend"]
WSS["WebSocket Server"]
Router["Router"]
Handler["Route Handler"]
end
HTML --> HTMX
HTMX -->|"hx-post, hx-get, etc."| WS
WS <-->|"WebSocket (No HTTP!)"| WSS
WSS --> Router
Router --> Handler
Handler -->|"HTML fragments"| WSS
WSS -->|"Response"| WS
WS -->|"DOM Update"| HTML
- Start β vxui finds a free port and starts a WebSocket server
- Launch β Detects and launches your system browser with the HTML file
- Connect β Browser connects to WebSocket server via
vxui-ws.js - Interact β User actions trigger WebSocket messages instead of HTTP requests
- Respond β V handlers return HTML fragments for dynamic updates
See examples/test/ for a complete form handling example with:
- Modern dark theme with glassmorphism
- Input validation
- Dynamic updates
- Edit/Cancel workflow
See examples/enchart/ for:
- Modern dashboard UI with dark theme
- ECharts integration with gradient charts
- Real-time data streaming with live statistics
- Stat cards showing current/average/peak values
See examples/gallery/ for a comprehensive UI controls demo:
- Buttons, forms, inputs
- Progress bars, tabs, tables
- Cards, modals, notifications
- Dark mode toggle
See examples/element-plus/ for Vue 3 + Element Plus integration:
- Professional UI components (Button, Form, Table, Dialog, etc.)
- Backend-driven notifications via
run_js() - Demonstrates vxui with modern Vue 3 ecosystem
Run examples:
cd examples/test
v run main.vUse run_js() to execute JavaScript in the browser and get results:
// Execute on first connected client
result := app.run_js('document.title', 5000)! // 5 second timeout
println('Page title: ${result}')
// Execute on specific client
result := app.run_js_client(client_id, 'alert("Hello!")', 3000)!Enable multiple browser connections:
fn main() {
mut app := App{}
app.multi_client = true // Allow multiple clients
vxui.run(mut app, './ui/index.html')!
}
// In your handlers:
fn (mut app App) broadcast_msg(msg map[string]json2.Any) string {
// Get all connected clients
clients := app.get_clients()
// Broadcast to all
app.broadcast('<div hx-swap-oob="true">Server update</div>')!
return '<div>Sent to ${clients.len} clients</div>'
}Control browser window size and position:
fn main() {
mut app := App{}
app.set_window_size(1200, 800)
app.set_window_position(-1, -1) // -1 = center
app.set_window_title('My Application')
vxui.run(mut app, './ui/index.html')!
}Embed frontend files into the binary for easy distribution:
module main
import vxui
// Embed frontend files at compile time
const index_html = $embed_file('ui/index.html')
const app_js = $embed_file('ui/app.js')
const style_css = $embed_file('ui/style.css')
struct App {
vxui.Context
}
fn main() {
mut app := App{}
// Create packed app with embedded files
mut packed := vxui.new_packed_app()
packed.add_file_string('index.html', index_html.to_string())
packed.add_file_string('app.js', app_js.to_string())
packed.add_file_string('style.css', style_css.to_string())
// Run with packed resources
vxui.run_packed(mut app, mut packed, 'index.html')!
}Build single executable:
v -prod main.v # Production build (~1.4 MB)
v -prod -compress main.v # Compressed build (smaller)Result: A single .exe file containing all frontend assets!
vxui includes several security features:
- Token Authentication β Auto-generated security token for client verification
- XSS Protection β Built-in HTML/JS escaping functions
- Path Traversal Prevention β Input sanitization
- No External Network β WebSocket only binds to localhost
Every connection requires token verification:
fn main() {
mut app := App{}
// Token is auto-generated, or set manually:
// app.token = 'my-secret-token'
// Get token for debugging
println('Token: ${app.get_token()}')
vxui.run(mut app, './ui/index.html')!
}import vxui
fn (mut app App) handler(msg map[string]json2.Any) string {
user_input := msg['name'] or { '' }.str()
safe := vxui.escape_html(user_input)
return '<div>Hello ${safe}</div>'
}- Always validate input β Use
sanitize_path()for file paths - Escape output β Use
escape_html(),escape_js(),escape_attr() - Keep tokens secure β Tokens are auto-generated and passed via URL
- Limit JS execution β Configure
js_sandboxsettings appropriately
fn main() {
mut app := App{}
// Enhanced security configuration
app.config.js_sandbox = vxui.JsSandboxConfig{
enabled: true
timeout_ms: 3000
max_result_size: 1024 * 100 // 100KB
allow_eval: false
}
vxui.run(mut app, './ui/index.html')!
}Deprecated fields removed:
app.tokenβ Useapp.config.tokenapp.multi_clientβ Useapp.config.multi_client
// Before (v0.5.x)
mut app := App{}
app.token = 'my-token'
app.multi_client = true
// After (v0.6.0)
mut app := App{}
app.config.token = 'my-token'
app.config.multi_client = trueConfiguration unified:
- All settings now in
app.config - Window, browser, JS sandbox settings available
// Before
app.window = vxui.WindowConfig{width: 1200, height: 800}
// After
app.config.window = vxui.WindowConfig{width: 1200, height: 800}vxui auto-detects and supports:
| Browser | Linux | macOS | Windows |
|---|---|---|---|
| Chrome | β | β | β |
| Chromium | β | β | β |
| Edge | β | β | β |
| Firefox | β | β | β |
| Brave | β | β | β |
- API Reference β Auto-generated from source
- Architecture Guide β Internal design documentation
We welcome contributions! Please see CONTRIBUTING.md for guidelines.
# Clone the repo
git clone https://github.com/kbkpbot/vxui.git
cd vxui
# Run tests
v test vxui_test.v
# Format code
v fmt -w .This project is licensed under the MIT License.
- V Language β The amazing language powering vxui
- htmx β The frontend library for dynamic HTML
vxui is currently in alpha stage. APIs may change, and some features are still being developed. Please report any issues you encounter!




