RxHTML Fundamentals
RxHTML compiles to JavaScript and runs in the browser. But unlike normal HTML where you have a single document, RxHTML uses a forest structure — multiple pages and reusable templates all living under one roof.
The Forest Structure
In RxHTML, the root element isn't <html>. Instead, you create a <forest> that holds your pages and templates:
<forest>
<page uri="/">
<h1>Welcome</h1>
</page>
<page uri="/about">
<h1>About Us</h1>
</page>
<template name="header">
<nav>Navigation here</nav>
</template>
</forest>
A forest can span multiple .rx.html files. The compiler merges them all into a single application, which is nice because you can organize things however makes sense to you.
Pages
Pages are full-screen documents routable via URI. Each one declares what URL path activates it:
<forest>
<page uri="/">
<h1>Home Page</h1>
</page>
<page uri="/products">
<h1>Our Products</h1>
</page>
<page uri="/contact">
<h1>Contact Us</h1>
</page>
</forest>
URI Parameters
URIs support dynamic parameters using the $ prefix:
<forest>
<page uri="/product/$id">
<h1>Product Details</h1>
<p>Viewing product: <lookup path="view:id" /></p>
</page>
<page uri="/user/$username/posts/$post_id">
<h1>Blog Post</h1>
</page>
</forest>
Parameters get extracted from the URL and dropped into the view state. Access them with the view: prefix:
| URL | View State |
|---|---|
/product/42 |
view:id = "42" |
/user/alice/posts/7 |
view:username = "alice", view:post_id = "7" |
Authentication Requirements
Pages can require authentication:
<forest>
<page uri="/dashboard" authenticate>
<!-- Only accessible when logged in -->
<h1>Your Dashboard</h1>
</page>
<page uri="/login" default-redirect-source>
<!-- Unauthenticated users end up here -->
<h1>Please Log In</h1>
</page>
</forest>
The authenticate attribute marks a page as requiring auth. If someone shows up without being logged in, they get redirected to whatever page has default-redirect-source. Simple enough.
Templates
Templates are reusable UI components — shared HTML you can invoke from pages or other templates:
<forest>
<template name="site-header">
<header>
<h1>My Application</h1>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>
</template>
<page uri="/">
<div rx:template="site-header"></div>
<main>
<p>Welcome to the home page!</p>
</main>
</page>
<page uri="/about">
<div rx:template="site-header"></div>
<main>
<p>About our company...</p>
</main>
</page>
</forest>
Template Invocation
Invoke a template with the rx:template attribute:
<div rx:template="my-template">
<!-- This content becomes available via <fragment /> -->
</div>
The element carrying rx:template becomes a container. Its children get passed to the template and are accessible via the <fragment> element.
The Fragment Element
Templates can inject the caller's children using <fragment>:
<forest>
<template name="card">
<div class="card">
<div class="card-header">
<fragment case="title" />
</div>
<div class="card-body">
<fragment />
</div>
</div>
</template>
<page uri="/">
<div rx:template="card">
<span rx:case="title">Welcome</span>
<p>This is the card body content.</p>
</div>
</page>
</forest>
The case attribute on <fragment> and rx:case on children give you named slots. So you can pass different chunks of content to different parts of a template. It's essentially slot-based composition without the ceremony.
Inline Templates
If you don't want a wrapper element cluttering up your DOM, use <inline-template>:
<page uri="/">
<inline-template name="site-header">
<!-- Children merged directly into parent -->
</inline-template>
<main>Content</main>
</page>
The Two Reactive Trees
This is the part that matters most. RxHTML maintains two separate data sources:
View State (Client)
The view state belongs to the client. It holds:
- UI state (selected tabs, expanded sections, filters)
- Form values before submission
- URI parameters
- Temporary client-side data
Access it with the view: prefix:
<lookup path="view:search_query" />
View state gets sent to the Adama document asynchronously. Your Adama code reads it via @viewer:
record Item {
public string title;
}
table<Item> _items;
view string filter;
// In Adama
bubble filtered_items = iterate _items
where @viewer.filter == "" || title.contains(@viewer.filter);
Data State (Server)
The data state comes from the Adama document over the WebSocket. It holds:
- Document fields
- Table data
- Bubble results computed for the current viewer
Access it directly (no prefix), or explicitly with data::
<lookup path="title" />
<lookup path="data:title" /> <!-- equivalent -->
Path Navigation
Paths navigate the hierarchical data structure:
| Syntax | Meaning |
|---|---|
field |
Access field in current scope |
/field |
Navigate to document root, then access field |
../field |
Navigate to parent, then access field |
child/field |
Navigate into child object, then access field |
view:path |
Access view state instead of data state |
data:path |
Explicitly access data state |
The Shell
A forest can have one <shell> element that configures the generated application:
<forest>
<shell>
<meta charset="UTF-8">
<title>My Application</title>
<link rel="stylesheet" href="/styles.css">
<script src="/custom.js"></script>
</shell>
<page uri="/">
<!-- Page content -->
</page>
</forest>
The shell defines your HTML head content — stylesheets, scripts, meta tags, and the default title.
File Organization
RxHTML applications typically organize files by concern:
frontend/
shell.rx.html # Shell and global configuration
templates.rx.html # Shared templates
pages/
home.rx.html # Home page
products.rx.html # Product pages
auth.rx.html # Login/signup pages
All .rx.html files in your frontend directory get merged into a single forest. Organize however you like; the compiler doesn't care about your folder structure.
Now go learn how to connect to Adama and bind data.