The Lux Language Specification
Version 0.1.0 β March 2026 Author: David J. Charlot, PhD β Open Interface Engineering, Inc. Status: Draft
1. Introduction
Lux is a web programming language that unifies structure, style, logic, and data into a single syntax. It replaces HTML, CSS, JavaScript, TypeScript, and the DOM API with one language that compiles to native code and WebAssembly.
Energy is a first-class observable in Lux: programs produce an energy receipt alongside their output. Aggregate units of work (renders, routes, fetches, state updates) are measured from real hardware power sensors where the host provides them, and costed from a calibrated per-platform model where it does not β each figure carrying its measurement source. (See the whitepaper, Β§5, for the measurement stack and its resolution limits.)
Lux source files use the .lux extension.
1.1 Design Principles
- One language β No separation of markup, style, and logic. A
.luxfile is a complete program. - Energy-first β Every expression, render cycle, and I/O operation is metered in joules.
- Sequential clarity β Familiar imperative syntax. No new paradigm to learn.
- Type-safe by default β Gradual typing with full inference. Add annotations when you want, not because you must.
- No null β Option types (
T?) replace null. The billion-dollar mistake does not exist in Lux. - AI-friendly β Small grammar, no ambiguity, deterministic parsing. AI code generators produce correct Lux on the first attempt.
- Zero-dependency standard library β 1,723 modules ship with the runtime. No package manager needed for common tasks.
1.2 Targets
- Browser: Compiles to WebAssembly (< 400 KB)
- Server: Compiles to native binary via Cranelift
- Edge: Compiles to minimal WASM or native for constrained devices
- Desktop: Native binary with platform windowing
2. Lexical Structure
2.1 Character Set
Lux source files are UTF-8 encoded. Identifiers support Unicode letters and digits.
2.2 Comments
# This is a line comment
#* This is a
block comment *#
2.3 Indentation
Lux uses indentation to delimit blocks. The lexer emits INDENT and DEDENT tokens. The reference indentation unit is 2 spaces. Tabs are not permitted.
A colon (:) at the end of a line opens a new indentation block. The next line must be indented further than the current level.
if x > 0:
print("positive")
print("and non-zero")
2.4 Tokens
2.4.1 Keywords
app server view style use effect
let if else for in while
match return break continue fn and
or not true false none signal
memo batch get post put delete
patch import from as is with
yield await spawn try catch throw
2.4.2 Operators
| Operator | Meaning |
|---|---|
+ - * / % | Arithmetic |
== != < <= > >= | Comparison |
and or not | Logical |
= | Assignment |
-> | Arrow (lambdas, event handlers) |
. | Field access |
|> | Pipe (left-to-right function composition) |
? | Optional chaining / type optionality |
.. | Range |
++ | String/list concatenation |
2.4.3 Delimiters
( ) # grouping, function calls
[ ] # list literals, indexing
{ } # record literals, string interpolation
: # block opener, key-value separator
, # separator in lists, records, parameters
2.4.4 Literals
42 # int
3.14 # float
"hello" # string
"hello, {name}" # interpolated string
true / false # bool
none # none value
[1, 2, 3] # list
{name: "David"} # record
2.5 Identifiers
Identifiers begin with a Unicode letter or underscore, followed by letters, digits, or underscores. Identifiers are case-sensitive.
x
user_name
_private
Counter
rΓ©sumΓ©
ζ₯ζ¬θͺ
2.6 String Interpolation
Strings delimited by double quotes support interpolation with {expr}:
name = "world"
greeting = "hello, {name}" # "hello, world"
math = "2 + 2 = {2 + 2}" # "2 + 2 = 4"
nested = "{user.name} ({user.age})" # "David (40)"
Curly braces in strings are escaped with \{ and \}.
Triple-quoted strings preserve whitespace and support multi-line content:
html = """
<div>
<p>preserved indentation</p>
</div>
"""
3. Type System
3.1 Base Types
| Type | Description | Example |
|---|---|---|
int | 64-bit signed integer | 42 |
float | 64-bit IEEE 754 | 3.14 |
str | UTF-8 string | "hello" |
bool | Boolean | true, false |
none | Absence of value | none |
3.2 Compound Types
| Type | Syntax | Example |
|---|---|---|
| List | [T] | [1, 2, 3] |
| Record | {field: T, ...} | {name: "David", age: 40} |
| Tuple | (T, U, ...) | (1, "hello") |
| Option | T? | str? β string or none |
| Function | fn(T, U) -> V | fn(int) -> str |
3.3 Type Inference
All types are inferred by default. Annotations are optional:
# Inferred
x = 42 # int
name = "David" # str
items = [1, 2, 3] # [int]
user = {name: "David"} # {name: str}
# Annotated
x: int = 42
name: str = "David"
items: [int] = [1, 2, 3]
3.4 Option Types (No Null)
There is no null in Lux. A value that may be absent has type T?:
find_user(id: int) -> {name: str}?:
if id == 0:
none
else:
{name: "David"}
user = find_user(1)
print(user?.name) # optional chaining β prints name or does nothing
print(user ?? "default") # coalescing β provides fallback
3.5 User-Defined Types
type Point:
x: float
y: float
type Shape:
Circle(radius: float)
Rect(width: float, height: float)
Triangle(a: Point, b: Point, c: Point)
type Result[T, E]:
Ok(value: T)
Err(error: E)
3.6 Type Conversions
Explicit only. No implicit coercion between numeric types:
x: int = 42
y: float = x.to_float() # explicit
z: str = x.to_str() # explicit
4. Expressions
4.1 Precedence (highest to lowest)
| Level | Operators | Associativity |
|---|---|---|
| 1 | . [i] (args) | Left |
| 2 | -x not x | Right (unary) |
| 3 | * / % | Left |
| 4 | + - ++ | Left |
| 5 | |> | Left |
| 6 | .. | None |
| 7 | == != < <= > >= is | Left |
| 8 | and | Left |
| 9 | or | Left |
| 10 | -> | Right |
4.2 Everything is an Expression
if, match, and blocks return values:
status = if active: "on" else: "off"
label = match kind:
"a" -> "Alpha"
"b" -> "Beta"
_ -> "Unknown"
result = do:
x = compute()
x * 2
4.3 Pipe Operator
Left-to-right function composition:
result = data
|> filter(item -> item.active)
|> map(item -> item.name)
|> sort()
|> take(10)
4.4 Range Expressions
1..10 # exclusive: 1, 2, ..., 9
1..=10 # inclusive: 1, 2, ..., 10
5. Statements
5.1 Let Bindings
let name = "David" # immutable (default)
let mut count = 0 # mutable
let x: int = 42 # annotated
The let keyword is optional for top-level and block-level bindings:
name = "David" # equivalent to let name = "David"
mut count = 0 # equivalent to let mut count = 0
5.2 Assignment
Only mut bindings can be reassigned:
mut count = 0
count = count + 1 # ok
name = "David"
name = "other" # error: name is immutable
5.3 If / Else
if temperature > 100:
print("hot")
else if temperature > 50:
print("warm")
else:
print("cold")
5.4 For Loops
for item in items:
print(item)
for item, index in items:
print("{index}: {item}")
for i in 0..10:
print(i)
5.5 While Loops
while count < 10:
count = count + 1
5.6 Match
match shape:
Circle(r) -> pi * r * r
Rect(w, h) -> w * h
_ -> 0.0
5.7 Return / Break / Continue
fn find(items, target):
for item in items:
if item == target:
return item
none
6. Functions
6.1 Function Definitions
# Multi-line
fn greet(name: str) -> str:
"Hello, {name}!"
# Single-expression (= syntax)
fn double(x) = x * 2
# Default parameters
fn connect(host: str, port: int = 8080):
# ...
6.2 Lambda Expressions
fn square = x -> x * x
items.filter(item -> item.active)
items.sort_by((a, b) -> a.name < b.name)
6.3 Closures
Lambdas capture variables from their enclosing scope:
fn make_counter():
mut count = 0
increment = () -> count = count + 1; count
increment
6.4 Positional Arguments and Default Parameters
Calls are positional. Parameters may declare defaults, which apply when an argument is omitted from the tail of the call.
fn create_user(name: str, age: int, active: bool = true):
{name, age, active}
create_user("David", 40) # active defaults to true
create_user("David", 40, false) # active given explicitly
Named/keyword call arguments (
create_user(name: "David", age: 40)) are not part of the current grammar. Arguments bind by position.
7. Reactive State
Lux provides reactive primitives as first-class language features, not library imports.
7.1 Signals
A signal is a reactive value that notifies dependents when it changes:
count = signal(0) # create signal with initial value 0
print(count) # read: 0
count.set(5) # write: dependents notified
count.update(n -> n + 1) # update: read + transform + write
7.2 Memo (Derived State)
A memo is a computed value that automatically re-evaluates when its dependencies change:
count = signal(0)
doubled = memo -> count * 2
count.set(5)
print(doubled) # 10 β automatically recomputed
7.3 Effect (Side Effects)
An effect runs whenever its reactive dependencies change:
count = signal(0)
effect:
print("Count is now {count}")
count.set(1) # prints: "Count is now 1"
count.set(2) # prints: "Count is now 2"
7.4 Batch
Multiple signal updates can be batched into a single notification cycle:
batch:
first_name.set("David")
last_name.set("Charlot")
# dependents notified once, not twice
8. View System
The view system replaces HTML + CSS + DOM manipulation with a declarative component model.
8.1 Elements
Elements are created by name, followed by positional arguments, named properties, and an optional child block:
text "Hello, World"
text "Count: {count}" size=24 color=blue
button "Click me" on:click -> handle_click()
8.2 Layout Elements
column gap=16 padding=24:
text "Title" size=32 weight=bold
row gap=8:
button "Save" on:click -> save()
button "Cancel" on:click -> cancel()
8.3 Built-in Elements
| Element | Purpose | Example |
|---|---|---|
text | Display text | text "hello" size=16 |
column | Vertical flex layout | column gap=8: |
row | Horizontal flex layout | row gap=8: |
box | Generic container | box padding=16: |
button | Clickable button | button "Submit" on:click -> ... |
input | Text input | input bind=name placeholder="Name" |
checkbox | Boolean input | checkbox bind=active |
select | Dropdown | select bind=choice options=items |
image | Image display | image src=url alt="photo" |
link | Navigation link | link "/about" text="About" |
list | Scrollable list | list items=data: |
grid | CSS Grid layout | grid cols=3 gap=8: |
canvas | 2D drawing surface | canvas width=800 height=600 |
8.4 Event Handling
Events use the on:event -> handler syntax:
button "Click" on:click -> count.update(n -> n + 1)
input bind=query on:change -> search(query)
box on:hover -> show_tooltip()
on:leave -> hide_tooltip()
8.5 Two-Way Binding
The bind property creates a two-way connection between an input and a signal:
name = signal("")
input bind=name placeholder="Enter name"
# Typing in the input updates the signal
# Changing the signal updates the input
8.6 Conditional Rendering
view:
if logged_in:
text "Welcome, {user.name}"
else:
button "Log in" on:click -> login()
8.7 List Rendering
view:
for todo, i in todos:
row gap=8 key=i:
checkbox bind=todo.done
text todo.text strike=todo.done
The key property is required for list items to enable efficient diffing.
8.8 Style Properties
Style properties are set directly on elements as named properties:
text "Title"
size=32
weight=bold
color="#1a1a1a"
font="Inter"
box
padding=24
background="#ffffff"
border_radius=8
shadow="0 2px 8px rgba(0,0,0,0.1)"
max_width=600
8.9 Named Styles
style card:
padding = 16
background = "#ffffff"
border_radius = 8
shadow = "0 2px 4px rgba(0,0,0,0.1)"
style card_hover:
shadow = "0 4px 12px rgba(0,0,0,0.15)"
view:
box style=card on:hover -> style=card_hover:
text "Card content"
8.10 Responsive Styles
style container:
padding = 24
max_width = 1200
@width < 768:
padding = 12
max_width = "100%"
@width < 480:
padding = 8
9. Components
9.1 App Components
An app defines a root component:
app Counter:
count = signal(0)
view:
column gap=16:
text "Count: {count}" size=24
button "+" on:click -> count.update(n -> n + 1)
9.2 Reusable Components
Components are functions that return views:
fn Card(title: str, children):
box style=card:
text title size=18 weight=bold
column gap=8:
children
# Usage
view:
Card "My Title":
text "Content inside the card"
button "Action" on:click -> do_something()
9.3 Component State
Each component instance has its own reactive scope:
fn TodoItem(todo):
editing = signal(false)
view:
if editing:
input bind=todo.text on:blur -> editing.set(false)
else:
text todo.text on:dblclick -> editing.set(true)
9.4 Component Composition
fn App():
todos = signal([])
view:
column:
Header()
for todo in todos:
TodoItem(todo)
Footer(count: todos.filter(t -> not t.done).len())
10. Server
Lux includes server-side syntax for HTTP APIs.
10.1 Server Definition
server Api port=8080:
db = connect("jouledb://localhost:5432")
get "/":
json({status: "ok", energy: energy.total()})
get "/users":
users = db.query("SELECT * FROM users")
json(users)
get "/users/{id}":
user = db.query("SELECT * FROM users WHERE id = ?", id)
if user:
json(user)
else:
status(404)
json({error: "not found"})
post "/users":
user = body()
db.query("INSERT INTO users (name, email) VALUES (?, ?)", user.name, user.email)
status(201)
json({status: "created"})
10.2 Middleware
server Api port=8080:
use cors(origins: ["*"])
use logger()
use energy_receipt() # adds X-Energy-Joules header
get "/":
json({status: "ok"})
10.3 Request Context
Inside a route handler, the following are available:
| Name | Type | Description |
|---|---|---|
body() | record | Parsed request body (JSON) |
params | record | URL path parameters |
query | record | URL query parameters |
headers | record | Request headers |
method | str | HTTP method |
path | str | Request path |
10.4 WebSocket
server Realtime port=8081:
ws "/chat":
on:connect -> print("client connected")
on:message(msg) -> broadcast(msg)
on:close -> print("client disconnected")
11. Module System
11.1 Files are Modules
Every .lux file is a module. The file name (without extension) is the module name.
# file: utils.lux
fn format_date(timestamp: int) -> str:
# ...
fn clamp(value, min, max):
if value < min: min
else if value > max: max
else: value
11.2 Use Statements
use ./utils # local file (relative path)
use ./components/header # nested local file
use lux:chart # standard library module
use lux:crypto/aes # standard library submodule
use ./utils { format_date, clamp } # selective import
use lux:chart as viz # aliased import
11.3 Standard Library
Lux ships with 1,723 modules organized by domain:
| Domain | Examples | Count |
|---|---|---|
| Core Web | vdom, reactive, component, router, forms, css | 30 |
| UI Elements | animation, drag, gesture, toast, carousel, tabs | 40 |
| Graphics | canvas2d, webgl, scene3d, mesh, lighting, particle | 25 |
| Audio | synthesizer, audio_fx, midi, spatial_audio, equalizer | 15 |
| Data Viz | chart, gantt, sankey, voronoi, force_layout, map_engine | 30 |
| Editors | rich_editor, syntax_editor, diff_editor, spell_check | 15 |
| Crypto | aes, crypto, jwt_codec, webauthn, oauth2_pkce | 15 |
| ML/AI | tensor, embedding, vision_pipeline, pose_detect, nlp_token | 15 |
| Data | csv, xml, json_patch, pdf, zip, xlsx, markdown | 20 |
| Typography | text_shaper, bidi, hyphenation, ligature, opentype | 10 |
| Financial | black_scholes, yield_curve, monte_carlo_var, portfolio_opt | 25 |
| Bioinformatics | needleman_wunsch, blast, phylogenetic, allele_freq | 20 |
| GIS | coordinate_transform, great_circle, geohash, wkt_parse | 20 |
| Post-Quantum | kyber_kem, dilithium_sign, sphincs_plus, lattice | 15 |
| CAD/CAM | nurbs_surface, boolean_csg, stl_io, mesh_repair | 20 |
| Healthcare | hl7_fhir, icd_code, drug_interaction, clinical_decision | 15 |
| Game Engine | ecs, physics2d, behavior_tree, pathfinding, sprite | 50+ |
| Testing | dom_test, visual_regression, property_test, mock_http | 20 |
| Build Tools | bundler, minifier, transpiler, source_map, hot_reload | 15 |
| DevOps | profiler, debugger, flame_graph, memory_profiler | 15 |
| β¦ | (1,200+ more modules) | β¦ |
No package manager is needed for these β they ship with the Lux runtime.
11.4 External Packages
# lux.toml
[project]
name = "myapp"
version = "0.1.0"
[dependencies]
my-charts = { git = "https://github.com/user/my-charts", tag = "v1.0" }
External packages are resolved by content hash (like Nix), not semver ranges.
12. Energy System
12.1 Ambient Energy Metering
Every operation in Lux is automatically measured in joules. No opt-in required.
energy.total() # total joules since program start
energy.last() # joules for the last operation
energy.receipt() # detailed breakdown by category
12.2 Energy Receipts
An energy receipt is a record with the following structure:
{
total_joules: float,
compute_joules: float,
render_joules: float,
io_joules: float,
network_joules: float,
operations: int,
duration_ms: float,
avg_watts: float,
}
12.3 Energy Budgets
Programs can declare energy budgets:
app Main:
energy.budget(max_joules: 1.0) # 1 joule per user interaction
view:
# if a render cycle exceeds 1J, a warning is emitted
12.4 Energy in HTTP Responses
Server routes automatically include an X-Energy-Joules header:
HTTP/1.1 200 OK
X-Energy-Joules: 0.000342
Content-Type: application/json
{"users": [...]}
12.5 Energy Display Widget
A built-in widget shows live energy consumption:
view:
energy_meter position=bottom_right
13. Concurrency
13.1 Async / Await
fn fetch_users() -> [User]:
response = await fetch("https://api.example.com/users")
await response.json()
13.2 Spawn
spawn:
process_data(large_dataset)
print("done processing")
13.3 Channels
ch = channel()
spawn:
for item in items:
ch.send(item)
ch.close()
for item in ch:
process(item)
14. Error Handling
14.1 Try / Catch
try:
data = await fetch_data()
process(data)
catch err:
print("Error: {err}")
14.2 Result Type
Functions that can fail return Result[T, E]:
fn parse_int(s: str) -> Result[int, str]:
# ...
match parse_int("42"):
Ok(n) -> print("got {n}")
Err(e) -> print("error: {e}")
14.3 The ? Operator
Propagates errors automatically:
fn load_config() -> Result[Config, str]:
text = read_file("config.toml")?
parse_toml(text)?
15. CLI Interface
The Lux CLI provides the complete development toolchain:
lux new myapp # create new project
lux dev # development server with hot reload
lux build # compile to native binary
lux build --wasm # compile to WebAssembly
lux run app.lux # run a Lux program
lux repl # interactive REPL
lux test # run tests
lux fmt # format source files
lux check # type-check without compiling
lux energy report.lux # energy profile of a program
lux add lux:chart # add standard library module to project
16. Grammar (EBNF)
module = { item } ;
item = app_def | server_def | func_def | style_def
| use_def | type_def | let_stmt ;
app_def = "app" IDENT ":" INDENT { stmt } [ view_block ] DEDENT ;
server_def = "server" IDENT { prop_assign } ":" INDENT { route | stmt } DEDENT ;
func_def = "fn" IDENT "(" [ params ] ")" [ "->" type ] ":" INDENT { stmt } DEDENT
| "fn" IDENT "(" [ params ] ")" "=" expr ;
style_def = "style" IDENT ":" INDENT { style_prop | responsive } DEDENT ;
use_def = "use" path [ "{" ident_list "}" ] [ "as" IDENT ] ;
type_def = "type" IDENT [ "[" type_params "]" ] ":" INDENT { variant | field } DEDENT ;
view_block = "view" ":" INDENT { view_node } DEDENT ;
view_node = IDENT { expr } { prop_assign } { event_bind } [ ":" INDENT { view_node } DEDENT ] ;
route = method STRING ":" INDENT { stmt } DEDENT ;
method = "get" | "post" | "put" | "delete" | "patch" ;
stmt = let_stmt | assign_stmt | if_stmt | for_stmt | while_stmt
| match_stmt | return_stmt | break_stmt | continue_stmt
| effect_stmt | expr_stmt ;
let_stmt = [ "let" ] [ "mut" ] IDENT [ ":" type ] "=" expr ;
assign_stmt = expr "=" expr ;
if_stmt = "if" expr ":" INDENT { stmt } DEDENT [ "else" ":" INDENT { stmt } DEDENT ] ;
for_stmt = "for" IDENT [ "," IDENT ] "in" expr ":" INDENT { stmt } DEDENT ;
while_stmt = "while" expr ":" INDENT { stmt } DEDENT ;
match_stmt = "match" expr ":" INDENT { match_arm } DEDENT ;
match_arm = pattern "->" expr ;
return_stmt = "return" [ expr ] ;
effect_stmt = "effect" ":" INDENT { stmt } DEDENT ;
expr = or_expr ;
or_expr = and_expr { "or" and_expr } ;
and_expr = not_expr { "and" not_expr } ;
not_expr = "not" not_expr | comparison ;
comparison = additive { ( "==" | "!=" | "<" | "<=" | ">" | ">=" | "is" ) additive } ;
additive = multiplicative { ( "+" | "-" | "++" ) multiplicative } ;
multiplicative = unary { ( "*" | "/" | "%" ) unary } ;
unary = ( "-" | "not" ) unary | postfix ;
postfix = primary { "." IDENT | "[" expr "]" | "(" [ args ] ")" | "?" } ;
primary = INT | FLOAT | STRING | "true" | "false" | "none"
| IDENT | "(" expr ")" | list_lit | record_lit
| "signal" "(" expr ")" | "memo" "->" expr
| lambda | if_expr | match_expr | "do" ":" block ;
lambda = IDENT "->" expr | "(" params ")" "->" expr ;
if_expr = "if" expr ":" expr "else" ":" expr ;
match_expr = "match" expr ":" INDENT { match_arm } DEDENT ;
list_lit = "[" [ expr { "," expr } ] "]" ;
record_lit = "{" [ field_init { "," field_init } ] "}" ;
field_init = IDENT ":" expr | IDENT ;
type = simple_type [ "?" ] ;
simple_type = IDENT | "[" type "]" | "(" type { "," type } ")"
| "{" field_type { "," field_type } "}" | "fn" "(" [ type_list ] ")" "->" type ;
field_type = IDENT ":" type ;
prop_assign = IDENT "=" expr ;
event_bind = "on" ":" IDENT "->" expr ;
params = param { "," param } ;
param = IDENT [ ":" type ] [ "=" expr ] ;
args = expr { "," expr } ;
path = ( "./" | IDENT ":" ) IDENT { "/" IDENT } ;
17. Compilation Model
17.1 Pipeline
.lux source
β Lexer (indentation-aware tokenizer)
β Parser (recursive descent + precedence climbing)
β AST
β Type checker (Hindley-Milner inference + gradual typing)
β IR (register-based SSA, ViewNode β VDOM ops, styles β CSS)
β Optimizer (constant folding, dead code, signal dependency analysis)
β Codegen (Cranelift for native, WASM for browser)
17.2 View Compilation
View blocks compile to VDOM builder calls:
# Lux source
view:
column gap=16:
text "Hello" size=24
# Compiles to (conceptual)
VNode::element("column")
.attr("gap", "16")
.child(
VNode::element("text")
.attr("size", "24")
.child(VNode::text("Hello"))
)
17.3 Reactive Compilation
Signals, memos, and effects compile to the reactive runtime:
# Lux source
count = signal(0)
doubled = memo -> count * 2
# Compiles to (conceptual)
let (count_r, count_w) = create_signal(0);
let doubled = create_memo(move || count_r.get() * 2);
17.4 Style Compilation
Style properties compile to optimized CSS:
# Lux source
text "Hello" size=24 color=blue weight=bold
# Emits CSS class
.lx_a { font-size: 24px; color: blue; font-weight: bold; }
CSS is deduplicated at compile time β identical style combinations share a single class.
18. Standard Library API Conventions
All standard library modules follow these conventions:
- Constructor:
Module.new(config)or direct function call - Fluent API: Methods return self for chaining
- Energy tracking: Every public method is metered
- JSON interop: All types serialize to/from JSON via
to_json()andfrom_json() - Error handling: Fallible operations return
Result[T, str]
Example:
use lux:black_scholes
price = black_scholes.price(
spot: 100.0,
strike: 105.0,
rate: 0.05,
vol: 0.2,
expiry: 1.0,
kind: "call"
)
greeks = black_scholes.greeks(spot: 100.0, strike: 105.0, rate: 0.05, vol: 0.2, expiry: 1.0)
print("Delta: {greeks.delta}, Gamma: {greeks.gamma}")
19. Interoperability
19.1 JavaScript Interop (Browser)
# Call JavaScript from Lux
extern fn alert(msg: str)
extern fn console_log(msg: str)
# Access DOM (escape hatch β prefer Lux view system)
extern fn document_query(selector: str) -> Element?
19.2 Rust Interop (Native)
Lux modules compile to Rust-compatible types. Rust crates can be used via:
use rust:serde_json
use rust:tokio
19.3 HTTP / REST
response = await fetch("https://api.example.com/data",
method: "POST",
headers: {"Content-Type": "application/json"},
body: {name: "David"}
)
data = await response.json()
20. Testing
20.1 Test Syntax
test "addition works":
assert 2 + 2 == 4
test "greeting includes name":
result = greet("David")
assert result == "Hello, David!"
test "signal updates":
count = signal(0)
count.set(5)
assert count == 5
20.2 Running Tests
lux test # run all tests
lux test utils.lux # run tests in a specific file
lux test --filter "addition" # filter by test name
Appendix A: Complete Example β Todo Application
# todo.lux β Complete todo application in Lux
app TodoApp:
todos = signal([])
input_text = signal("")
filter_mode = signal("all")
fn add_todo():
if input_text != "":
todos.update(list -> list ++ [{
text: input_text,
done: false,
id: list.len()
}])
input_text.set("")
fn toggle(id: int):
todos.update(list ->
list.map(t -> if t.id == id: {..t, done: not t.done} else: t)
)
fn remove(id: int):
todos.update(list -> list.filter(t -> t.id != id))
visible = memo ->
match filter_mode:
"all" -> todos
"active" -> todos.filter(t -> not t.done)
"done" -> todos.filter(t -> t.done)
remaining = memo -> todos.filter(t -> not t.done).len()
view:
column padding=24 max_width=600 margin="0 auto":
text "Todos" size=32 weight=bold color="#333"
row gap=8:
input bind=input_text
placeholder="What needs to be done?"
on:enter -> add_todo()
button "Add" on:click -> add_todo()
for todo in visible:
row gap=8 key=todo.id padding=8 border_bottom="1px solid #eee":
checkbox bind=todo.done on:change -> toggle(todo.id)
text todo.text
strike=todo.done
color=if todo.done: "#999" else: "#333"
button "x" on:click -> remove(todo.id)
color=red
opacity=0.5
row gap=16 padding=8:
text "{remaining} remaining" color="#666"
row gap=8:
button "All" on:click -> filter_mode.set("all")
weight=if filter_mode == "all": bold else: normal
button "Active" on:click -> filter_mode.set("active")
weight=if filter_mode == "active": bold else: normal
button "Done" on:click -> filter_mode.set("done")
weight=if filter_mode == "done": bold else: normal
energy_meter position=bottom_right
Appendix B: Comparison β Lux vs React+TypeScript
Lines of Code for a Todo App
| React + TypeScript + CSS | Lux | |
|---|---|---|
| Markup/View | 45 lines (JSX) | β |
| Logic | 35 lines (TypeScript) | β |
| Styles | 60 lines (CSS) | β |
| Imports/Config | 15 lines | β |
| Total | 155 lines across 3 files | 55 lines in 1 file |
| Dependencies | react, react-dom, typescript, webpack, babel, css-loader | 0 |
| node_modules | ~180 MB | 0 bytes |
| Bundle size | ~45 KB (minified + gzipped) | ~12 KB (WASM) |
| Energy per render | unmeasured | 0.000004 J |
Concept Mapping
| Web Stack | Lux |
|---|---|
| HTML elements | View elements (text, column, row, box) |
| CSS classes | Style properties on elements / named style blocks |
| CSS media queries | @width < 768: in style blocks |
| JavaScript | Lux expressions and statements |
| TypeScript types | Lux type annotations (optional) |
| React useState | signal() |
| React useMemo | memo -> |
| React useEffect | effect: |
| React components | fn Component(props): |
| JSX | View blocks |
| className | style=name |
| onClick | on:click -> |
| package.json | lux.toml |
| npm install | Not needed β 1,723 modules built in |
| webpack/vite | lux build |
| node_modules | Does not exist |