A programming language for building UIs
See the codeTint is a small declarative UI DSL for Rust/WASM applications, with a direct Rust-to-DOM rendering path.
ui fn App() {
Column {
layout::{ padding::24, gap::12 }
paint::{ background::#12141a }
Text { text::{24, bold, white} "Hello Tint" }
Button { paint::{ radius::8, color::white } "Click me" }
}
}
.tn) source: https://tint-gamma.vercel.app/ponglayout::{...}, paint::{...}, and motion::{...}; flat modifiers remain accepted for compatibilityif/else, for loops, functions with persistence, full comparison operators (< > <= >= == !=) alongside && ||state, click||, hover_in||/hover_out|| drive a persistent, re-rendering sessionmobile::{}/tablet::{}/laptop::{}/desktop::{} style breakpoints, plus a host-exposed viewport_width variable for structural if{} layout swaps (see tint-model/sandbox/src/landing/)grid::{ columns, rows, gap } modifier with minmax(0, 1fr)-friendly tracksroute||"/path" renders as a normal browser link, including / and /sandboxDomSession renders Tint through Rust/web-sys, without a UI frameworkTintVM::register_native("name", |args| ...) registers a real Rust closure that .tn source calls directly by name -- no Rust syntax inside the language, no reimplementing rustc's borrow checker, just an ordinary Rust function called across the boundary (see tint run <file> now_ms, a demo native in tint-cli)mod name; resolves to name.tn or name/mod.tn (the mod.rs convention); use path::to::item; + export control what crosses file boundaries (see examples/modules/)# Build the CLI
cd tint-model
cargo build -p tint-cli --release
export PATH="$PWD/target/release:$PATH"
# Check syntax
tint check app.tn
# Build a standalone HTML file (real embedded WASM, works offline from disk)
tint build app.tn [ui_fn] -o app.html
# Run in REPL
tint repl
Install from marketplace (coming soon) or manually:
cd vscode-tint
npm install && npm run compile
code --install-extension ./tint-lang-0.1.0.vsix
Open a .tn file
Press Cmd+Shift+B to build to HTML
cd tint-model/sandbox
npm install
npm run dev:all
Open browser to http://localhost:5173 and edit code live.
Status: the sandbox is a deliberately small workbench, not a full IDE. It currently uses
sandbox/src/sandbox.tn, CodeMirror, a live WASM preview, and direct DOM rendering throughDomSession.
tint/
├── tint-model/ # Core language implementation (Rust)
│ ├── crates/
│ │ ├── tint-lexer/ # Tokenization
│ │ ├── tint-parser/ # Parsing → AST
│ │ ├── tint-ast/ # AST nodes
│ │ ├── tint-semantics/ # Semantic analysis
│ │ ├── tint-ir/ # SSA IR + optimizer
│ │ ├── tint-evaluator/ # Value system + built-ins
│ │ ├── tint-runtime/ # VM + UI renderer
│ │ ├── tint-wasm/ # WASM bindings
│ │ └── tint-cli/ # Command-line tool
│ ├── examples/ # Sample .tn programs
│ └── sandbox/ # Tint-rendered Web IDE
└── vscode-tint/ # VSCode extension (TypeScript)
Tests live inside each crate's own tests/ directory (standard Rust layout),
not in a separate top-level folder -- tint-runtime/tests/ has the most,
covering the UI runtime, sessions, layout, and rendering.
✅ Proper compiler architecture (not hacked)
✅ UI grammar with modifiers
✅ Control flow (if/else, for loops, comparison operators)
✅ Function definitions & calls
✅ Persistent REPL sessions
✅ WASM compilation & rendering
✅ Real state + click/hover handlers (state, click||, hover_in||/hover_out||) via a persistent UiSession
✅ Responsive layouts (mobile::{}/tablet::{}/laptop::{}/desktop::{} breakpoints + structural if{ viewport_width ... })
✅ A second, direct-DOM rendering backend (DomSession, no JS framework)
✅ CLI tooling
✅ VSCode integration
✅ Hover effects & animations
✅ Grid layout and internal route links
✅ Automatic .tn reload and Rust/WASM rebuild with npm run dev:all
✅ A native fn called from inside a plain fn's own body, invoked the normal top-level way through the IR VM -- IrVM now falls back to a native-lookup callback (set_native_call) when no IR-compiled function matches the callee name, backed by TintVM::native_fns (see tint-runtime/tests/native_fn.rs)
✅ Multi-file modules (mod/use/export) via a tint-cli-side loader -- absolute paths only (no self::/super::), but grouped (use a::{b, c}), as-aliased, and wildcard (use a::*) imports all work alongside the plain form; fn/struct/enum are exportable
✅ A basic semantic checker (undefined-variable/duplicate-binding checks, not type checking) over fn, ui fn, and impl method bodies -- tint check fails on errors, tint run warns and still executes
❌ Multiple independent component instances (state is one flat scope per UiSession)
✅ match{} in UI trees, XML-mode only: <case label>...</case> children (<case _> as the wildcard arm), matched by comparing the scrutinee's display text against each label -- see tint-runtime/tests/render_ui.rs
✅ Compile-time .tn imports in the Vite sandbox source pipeline
❌ Namespace access (Ns::item) and lambda expressions are unimplemented specifically in the IR VM path (both already work through the tree-walking path used for UI handlers)
❌ Tuple-style enum variants (enum E { A(T) }) -- rejected at the semantic-check stage; only named-field variants (enum E { A { x } }) are supported
❌ Type annotations (inference-only)
❌ Generics
❌ Async/await
These are deliberate design choices for a proof of concept, not bugs. The architecture supports adding them without major changes.
Create app.tn:
ui fn App() {
Column {
layout::{ padding::24, gap::16 }
paint::{ background::#f5f7fa }
Text { text::{32, bold, #000} "Dashboard" }
Card { layout::{ padding::16 } paint::{ radius::12, background::white }
Row { gap::8
Text { text::{14, #666} "Status:" }
Text { text::{14, bold, #00aa00} "Online" }
}
}
for { item in ["Item 1", "Item 2", "Item 3"] } {
ListItem { layout::{ padding::12 } paint::{ radius::8, background::#eee }
Text { "{item}" }
}
}
}
}
Build & view:
tint build app.tn -o app.html
open app.html
Style breakpoints (mobile::{}/tablet::{}/laptop::{}/desktop::{}) restyle a node per
viewport width, and a host-exposed viewport_width variable lets if{} swap in structurally
different content — not just a different style. See tint-model/sandbox/src/landing/ for a full
page built this way (a nav that becomes a hamburger menu below 768px, a hero that stacks on
mobile), rendered through DomSession with no JS framework -- it's the site's own real landing
page (sandbox/src/main.tn mounted by a minimal sandbox/index.html), split into landing.tn, topbar.tn, hero.tn, demo.tn, and
footer.tn, with compile-time .tn imports and theme::dark/theme::light
blocks instead of duplicated comment-marked trees:
Nav {
layout::{ direction::row }
NavLinks { layout::{ direction::row, gap::24 } if{viewport_width >= 768} Text { "Docs" } }
MenuButton { click||toggle_menu layout::{ padding::10 } paint::{ radius::10 } if{viewport_width < 768} "Menu" }
}
Hero {
layout::{ direction::row, gap::40 }
mobile::{ direction::column, gap::24 }
HeroCopy { grow::1 Text { text::{44, bold, white} "..." } }
}
cd tint-model
# Native (native CLI)
cargo build -p tint-cli
# WASM (web sandbox)
cargo build -p tint-wasm --target wasm32-unknown-unknown --release
wasm-pack build crates/tint-wasm --release
# Tests
cargo test
Each compiler phase is independent:
No external DSL files—grammar is in code for easy modification.
MIT - see LICENSE for details.
Mark Bender (@hawerz)
Feedback welcome!
Rust
83.7%
JavaScript
14.6%
A programming language for building UIs
See the codeTint is a small declarative UI DSL for Rust/WASM applications, with a direct Rust-to-DOM rendering path.
ui fn App() {
Column {
layout::{ padding::24, gap::12 }
paint::{ background::#12141a }
Text { text::{24, bold, white} "Hello Tint" }
Button { paint::{ radius::8, color::white } "Click me" }
}
}
.tn) source: https://tint-gamma.vercel.app/ponglayout::{...}, paint::{...}, and motion::{...}; flat modifiers remain accepted for compatibilityif/else, for loops, functions with persistence, full comparison operators (< > <= >= == !=) alongside && ||state, click||, hover_in||/hover_out|| drive a persistent, re-rendering sessionmobile::{}/tablet::{}/laptop::{}/desktop::{} style breakpoints, plus a host-exposed viewport_width variable for structural if{} layout swaps (see tint-model/sandbox/src/landing/)grid::{ columns, rows, gap } modifier with minmax(0, 1fr)-friendly tracksroute||"/path" renders as a normal browser link, including / and /sandboxDomSession renders Tint through Rust/web-sys, without a UI frameworkTintVM::register_native("name", |args| ...) registers a real Rust closure that .tn source calls directly by name -- no Rust syntax inside the language, no reimplementing rustc's borrow checker, just an ordinary Rust function called across the boundary (see tint run <file> now_ms, a demo native in tint-cli)mod name; resolves to name.tn or name/mod.tn (the mod.rs convention); use path::to::item; + export control what crosses file boundaries (see examples/modules/)# Build the CLI
cd tint-model
cargo build -p tint-cli --release
export PATH="$PWD/target/release:$PATH"
# Check syntax
tint check app.tn
# Build a standalone HTML file (real embedded WASM, works offline from disk)
tint build app.tn [ui_fn] -o app.html
# Run in REPL
tint repl
Install from marketplace (coming soon) or manually:
cd vscode-tint
npm install && npm run compile
code --install-extension ./tint-lang-0.1.0.vsix
Open a .tn file
Press Cmd+Shift+B to build to HTML
cd tint-model/sandbox
npm install
npm run dev:all
Open browser to http://localhost:5173 and edit code live.
Status: the sandbox is a deliberately small workbench, not a full IDE. It currently uses
sandbox/src/sandbox.tn, CodeMirror, a live WASM preview, and direct DOM rendering throughDomSession.
tint/
├── tint-model/ # Core language implementation (Rust)
│ ├── crates/
│ │ ├── tint-lexer/ # Tokenization
│ │ ├── tint-parser/ # Parsing → AST
│ │ ├── tint-ast/ # AST nodes
│ │ ├── tint-semantics/ # Semantic analysis
│ │ ├── tint-ir/ # SSA IR + optimizer
│ │ ├── tint-evaluator/ # Value system + built-ins
│ │ ├── tint-runtime/ # VM + UI renderer
│ │ ├── tint-wasm/ # WASM bindings
│ │ └── tint-cli/ # Command-line tool
│ ├── examples/ # Sample .tn programs
│ └── sandbox/ # Tint-rendered Web IDE
└── vscode-tint/ # VSCode extension (TypeScript)
Tests live inside each crate's own tests/ directory (standard Rust layout),
not in a separate top-level folder -- tint-runtime/tests/ has the most,
covering the UI runtime, sessions, layout, and rendering.
✅ Proper compiler architecture (not hacked)
✅ UI grammar with modifiers
✅ Control flow (if/else, for loops, comparison operators)
✅ Function definitions & calls
✅ Persistent REPL sessions
✅ WASM compilation & rendering
✅ Real state + click/hover handlers (state, click||, hover_in||/hover_out||) via a persistent UiSession
✅ Responsive layouts (mobile::{}/tablet::{}/laptop::{}/desktop::{} breakpoints + structural if{ viewport_width ... })
✅ A second, direct-DOM rendering backend (DomSession, no JS framework)
✅ CLI tooling
✅ VSCode integration
✅ Hover effects & animations
✅ Grid layout and internal route links
✅ Automatic .tn reload and Rust/WASM rebuild with npm run dev:all
✅ A native fn called from inside a plain fn's own body, invoked the normal top-level way through the IR VM -- IrVM now falls back to a native-lookup callback (set_native_call) when no IR-compiled function matches the callee name, backed by TintVM::native_fns (see tint-runtime/tests/native_fn.rs)
✅ Multi-file modules (mod/use/export) via a tint-cli-side loader -- absolute paths only (no self::/super::), but grouped (use a::{b, c}), as-aliased, and wildcard (use a::*) imports all work alongside the plain form; fn/struct/enum are exportable
✅ A basic semantic checker (undefined-variable/duplicate-binding checks, not type checking) over fn, ui fn, and impl method bodies -- tint check fails on errors, tint run warns and still executes
❌ Multiple independent component instances (state is one flat scope per UiSession)
✅ match{} in UI trees, XML-mode only: <case label>...</case> children (<case _> as the wildcard arm), matched by comparing the scrutinee's display text against each label -- see tint-runtime/tests/render_ui.rs
✅ Compile-time .tn imports in the Vite sandbox source pipeline
❌ Namespace access (Ns::item) and lambda expressions are unimplemented specifically in the IR VM path (both already work through the tree-walking path used for UI handlers)
❌ Tuple-style enum variants (enum E { A(T) }) -- rejected at the semantic-check stage; only named-field variants (enum E { A { x } }) are supported
❌ Type annotations (inference-only)
❌ Generics
❌ Async/await
These are deliberate design choices for a proof of concept, not bugs. The architecture supports adding them without major changes.
Create app.tn:
ui fn App() {
Column {
layout::{ padding::24, gap::16 }
paint::{ background::#f5f7fa }
Text { text::{32, bold, #000} "Dashboard" }
Card { layout::{ padding::16 } paint::{ radius::12, background::white }
Row { gap::8
Text { text::{14, #666} "Status:" }
Text { text::{14, bold, #00aa00} "Online" }
}
}
for { item in ["Item 1", "Item 2", "Item 3"] } {
ListItem { layout::{ padding::12 } paint::{ radius::8, background::#eee }
Text { "{item}" }
}
}
}
}
Build & view:
tint build app.tn -o app.html
open app.html
Style breakpoints (mobile::{}/tablet::{}/laptop::{}/desktop::{}) restyle a node per
viewport width, and a host-exposed viewport_width variable lets if{} swap in structurally
different content — not just a different style. See tint-model/sandbox/src/landing/ for a full
page built this way (a nav that becomes a hamburger menu below 768px, a hero that stacks on
mobile), rendered through DomSession with no JS framework -- it's the site's own real landing
page (sandbox/src/main.tn mounted by a minimal sandbox/index.html), split into landing.tn, topbar.tn, hero.tn, demo.tn, and
footer.tn, with compile-time .tn imports and theme::dark/theme::light
blocks instead of duplicated comment-marked trees:
Nav {
layout::{ direction::row }
NavLinks { layout::{ direction::row, gap::24 } if{viewport_width >= 768} Text { "Docs" } }
MenuButton { click||toggle_menu layout::{ padding::10 } paint::{ radius::10 } if{viewport_width < 768} "Menu" }
}
Hero {
layout::{ direction::row, gap::40 }
mobile::{ direction::column, gap::24 }
HeroCopy { grow::1 Text { text::{44, bold, white} "..." } }
}
cd tint-model
# Native (native CLI)
cargo build -p tint-cli
# WASM (web sandbox)
cargo build -p tint-wasm --target wasm32-unknown-unknown --release
wasm-pack build crates/tint-wasm --release
# Tests
cargo test
Each compiler phase is independent:
No external DSL files—grammar is in code for easy modification.
MIT - see LICENSE for details.
Mark Bender (@hawerz)
Feedback welcome!
Rust
83.7%
JavaScript
14.6%