What is Pax?
Pax is a language-first GUI framework for Rust. A Pax application combines
declarative .pax templates, reactive properties and PAXEL expressions, Rust
application logic, and a portable runtime. The same interface model currently
targets web browsers, macOS, iOS, and iPadOS.
Pax is for builders who want the structure of an application framework without giving up direct control over layout, vector drawing, composition, and motion. It exists to widen the expressive range of software while keeping the source legible enough to reason about.
If you already know you want to try it, go directly to Getting Started. That chapter stands on its own; you do not need to read the rest of the documentation first.
A language built for interfaces
A Pax component usually has two source layers: a Rust type and a .pax
template. The template declares the component's interface tree, layout,
styling, bindings, control flow, event routing, responsive choices, and motion.
The Rust type exposes application state and owns handlers, data access,
platform integration, and other imperative work.
Inside a template, PAXEL expressions derive values from reactive properties. They can combine values, choose between alternatives, perform unit arithmetic, and feed the result into content, layout, styling, or animation. PAXEL is side-effect-free: it describes relationships between values rather than a sequence of commands.
Templates remain declarative and inspectable, while Rust remains available for everything that genuinely changes the world: responding to an event, loading data, writing a file, or updating application state. In practice, most components are understood by reading the template and Rust file together.
Try it: Living Quilt
Living Quilt is the project created by pax-cli create. Move the pointer to
shift its light, click or tap the quilt to send a wave of color through the
tiles, and click the Pax card to replay its entrance. The scene combines
responsive components, Rust-driven motion, lighting, and feathered alpha masks.
Its color reveal requires the GPU renderer.
The source tabs show how those parts fit together. You do not need to understand the whole scene to start editing it; Getting Started walks through a small change in the generated project.
The Pax authoring loop
The central loop has four beats:
.pax template ──renders──> interface ──event──> Rust handler
▲ │
│ │ sets
└── PAXEL derives visible values ◀── reactive properties
- A
.paxtemplate declares the interface and binds an event. - PAXEL expressions derive visible values from reactive properties.
- An interaction invokes a Rust handler.
- The handler updates a property, and Pax updates the dependent interface work.
When the property changes, Pax invalidates the values that depend on it and reevaluates them when needed. This is the sense in which Pax is spreadsheet-like: change an input, and the formulas downstream of it become the new interface. Rust can also construct computed properties and subscriptions directly when an application needs a relationship or effect beyond the template.
The later chapters separate these responsibilities in more detail: Template Language & Structure covers the declarative tree, Data Binding & Expressions covers PAXEL, State & Properties covers the reactive graph, and Event Handling & Rust Logic covers the imperative side of the loop.
One interface model, multiple native targets
Pax uses a shared compiler and runtime, then connects them to each platform through a target-specific chassis. At a high level, the compiler prepares the program, the runtime expands its reactive scene, and the chassis integrates rendering, input, native elements, and platform services.
How Pax Runs follows this process from source to the screen and explains how reactive updates and rendering work fit together.
The current application targets are:
- web browsers, through WebAssembly;
- macOS;
- iOS; and
- iPadOS.
Web development is supported from macOS, Debian/Ubuntu Linux, and Windows; Apple targets require a suitable macOS and Xcode environment. Getting Started has the current workstation setup.
A shared interface model also does not mean that every platform follows an identical rendering path. Web builds select WebGPU where browser policy and support permit it, with a Piet/CPU renderer where that path is required. Apple targets use their native chassis. Backend-specific capabilities must therefore state their target and fallback behavior rather than presenting one backend as universal.
Pax also integrates native elements where platform behavior matters. Native text, form controls, and scrolling can share a scene and coordinate space with rendered shapes, images, paths, masks, and motion. The runtime manages the boundary between those surfaces so application code can work with one component tree while still respecting target-specific behavior.
Why choose Pax?
The language is designed around interface work. Structure, reactive formulas, units, event bindings, conditional settings, and declarative motion live close to the elements they affect. Rust is not squeezed into the template language; it remains the application layer and the escape hatch for unrestricted logic.
Pax also puts ordinary application architecture and a higher creative ceiling in the same system. Components, state, routing, events, and responsive layout can inhabit the same scene as vector primitives, paths, gradients, masks, clipping, native-element composition, and animation. Builders do not have to switch to a separate presentation model as an interface becomes more visual.
Finally, the portable runtime is intended for shipping rather than previewing alone. It carries the reactive model across the current web and Apple targets while retaining native integration and target-aware rendering. Because the source and running scene are structured, the same model also supports tools for hot reload, inspection, screenshots, and repeatable event-driven checks for both human and agent-assisted workflows.
Current scope and maturity
Pax is ready for builders: it is coherent and runnable enough to evaluate, learn, and build real interfaces with today. It remains pre-1.0, so APIs can evolve, some areas are still limited, and feature or backend maturity can vary by target. “Ready for builders” is a transition in project maturity, not a claim that every application is production-ready without its own evaluation.
Rust is the current application language. Native text and controls, selection and editing, and image alternatives provide accessibility foundations, but broader work such as reading order, tab order, annotations, and comprehensive audits is still in progress.
The framework, language, compiler, runtime, CLI, hot reload, source mapping, inspection, screenshots, and event-driving infrastructure ship as open source in the Pax repository. No companion product is required to build, run, inspect, or ship a Pax application. Any future companion would add to that open-source workflow rather than unlock it.
Start building
Install the CLI, create a project, and run it.
If you prefer to explore before installing, browse the repository examples and return to the authoring loop above when you want to understand how their templates and Rust code fit together.
Getting Started
Pax is a UI framework for Rust, with targets for web, macOS, iOS, and iPadOS. This guide starts with the web target: install Pax, run a project in your browser, and get to know the files you'll work with.
Quick start
The CLI's telemetry policy and opt-out controls are described in CLI Telemetry.
If you have Rust, your operating system's build tools, the
wasm32-unknown-unknown target, and wasm-pack installed, run these commands in
your terminal:
cargo install pax-cli
pax-cli create my-first-project
cd my-first-project
pax-cli run --target=web
Open the local URL printed by the last command. Keep the terminal running while you use the app; press Ctrl-C there to stop it. Installation and the first build compile dependencies and can take some time.
Need the prerequisites? Start with Prepare your workstation. Already running? Continue to Project anatomy.
Prepare your workstation
You can develop Pax web applications on macOS, Debian/Ubuntu Linux, or Windows. This guide uses the web target. Pax also runs on macOS, iOS, and iPadOS; building for those Apple targets requires a macOS workstation with Xcode.
Choose the instructions for your workstation below. If Rust is already
installed, you can skip its installation commands. You'll still need the
WebAssembly target and wasm-pack for web builds.
macOS
Install Xcode Command Line Tools, then complete the installer before continuing:
xcode-select --install
If the tools are already installed, you can continue. Install Rust and the web build tools:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
. "$HOME/.cargo/env"
rustup target add wasm32-unknown-unknown
cargo install wasm-pack --version 0.15.0
Continue to Install the Pax CLI.
Linux (Debian / Ubuntu)
Install the system packages, Rust, and the web build tools:
# Install native build dependencies.
sudo apt update
sudo apt install -y \
ca-certificates curl git build-essential pkg-config libssl-dev \
python3 unzip xvfb \
libglib2.0-dev libcairo2-dev libpango1.0-dev
# Install Rust.
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
. "$HOME/.cargo/env"
# Install the WebAssembly target and helper.
rustup target add wasm32-unknown-unknown
cargo install wasm-pack --version 0.15.0
Continue to Install the Pax CLI.
Windows
Install Visual Studio Build Tools with the C++ workload, Git, Rust, and the web
build tools from PowerShell. The setup below also installs Clang for dependencies
that need it on ARM64 Windows. It uses winget to install Git and may prompt
for administrator approval.
Windows setup commands
Run these commands in order in the same PowerShell session:
# Install Visual Studio Build Tools.
$installer = "$env:TEMP\vs_BuildTools.exe"
Invoke-WebRequest https://aka.ms/vs/17/release/vs_BuildTools.exe -OutFile $installer
$vsArgs = @(
"--quiet",
"--wait",
"--norestart",
"--installPath", "C:\BuildTools",
"--add", "Microsoft.VisualStudio.Workload.VCTools",
"--add", "Microsoft.VisualStudio.Component.VC.Llvm.Clang",
"--add", "Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset",
"--includeRecommended"
)
if ($env:PROCESSOR_ARCHITECTURE -eq "ARM64") {
$vsArgs += @("--add", "Microsoft.VisualStudio.Component.VC.Tools.ARM64")
}
Start-Process $installer -Wait -ArgumentList $vsArgs
# Load the MSVC environment in this shell.
$vcvars = "C:\BuildTools\VC\Auxiliary\Build\vcvarsall.bat"
$vcArch = if ($env:PROCESSOR_ARCHITECTURE -eq "ARM64") { "arm64" } else { "x64" }
cmd.exe /s /c "`"$vcvars`" $vcArch >nul && set" | ForEach-Object {
$separator = $_.IndexOf("=")
if ($separator -gt 0) {
Set-Item -Path "Env:$($_.Substring(0, $separator))" `
-Value $_.Substring($separator + 1)
}
}
# Make Git and clang available to future shells.
winget install --id Git.Git --exact --source winget `
--accept-package-agreements --accept-source-agreements
$llvmPath = "C:\BuildTools\VC\Tools\Llvm\bin"
$userPath = @(
[Environment]::GetEnvironmentVariable("Path", "User") -split ";" |
Where-Object { $_ }
)
foreach ($path in @("C:\Program Files\Git\cmd", $llvmPath)) {
if ($userPath -notcontains $path) {
$userPath = @($userPath) + $path
}
}
[Environment]::SetEnvironmentVariable("Path", ($userPath -join ";"), "User")
$env:Path = "C:\Program Files\Git\cmd;$llvmPath;$env:Path"
# Install Rust.
$rustupArch = if ($env:PROCESSOR_ARCHITECTURE -eq "ARM64") {
"aarch64-pc-windows-msvc"
} else {
"x86_64-pc-windows-msvc"
}
$rustup = "$env:TEMP\rustup-init.exe"
Invoke-WebRequest "https://static.rust-lang.org/rustup/dist/$rustupArch/rustup-init.exe" -OutFile $rustup
& $rustup -y --profile default --default-toolchain stable
$env:Path = "$env:USERPROFILE\.cargo\bin;$env:Path"
# Install the WebAssembly target and helper.
rustup target add wasm32-unknown-unknown
cargo install wasm-pack --version 0.15.0
Continue in the same PowerShell session to Install the Pax CLI.
Install the Pax CLI
Cargo is Rust's package manager. Use it to install the published Pax CLI:
cargo install pax-cli
pax-cli --version
The version command confirms that your shell can find the installed CLI. Installing it compiles Pax and its dependencies, so expect compilation output before the command finishes.
If you completed the Quick Start, you can skip this installation and the next two steps.
Create a project
From the directory where you keep your projects, run:
pax-cli create my-first-project
Choose a destination that does not already exist. The CLI creates Living Quilt, an interactive geometric tapestry, including its Rust package, UI sources, and assets. You can try it in the introduction. The project source is bundled with your CLI version; no repository checkout is needed.
For a smaller starting point, use pax-cli create my-counter --example=increment.
The curated alternatives and their purpose are covered in
Developer Workflow.
Run it on the web
Enter the project directory and start the web target:
cd my-first-project
pax-cli run --target=web
The first run builds your application and its dependencies. Once the server is
ready, the terminal prints a local URL beginning with http://127.0.0.1:. Open
that URL in your browser. Use the address from your current run, since the port
can change between sessions.
Your first-run checkpoint is a geometric quilt with a Pax card in the middle. Click or tap the quilt to send out a color wave, or click the card to replay its entrance. The color reveal uses GPU alpha masking; check the rendering backend if that effect is absent. A blank page or a build error means there's still something to resolve; see When something goes wrong.
Leave the command running while you use the app. To stop the development session,
press Ctrl-C in its terminal. You can start it again with
pax-cli run --target=web from the project directory.
Project anatomy
Open the project in your editor. These are the main roles to recognize:
| File or directory | What it contains |
|---|---|
Cargo.toml | The Rust package definition, dependencies, and optional Pax project metadata. |
Rust source (.rs) | Component definitions, application state, event handlers, and other application logic. |
Pax templates (.pax) | UI elements, layout, styles, expressions, and bindings to event handlers. Templates can also be embedded in Rust source. |
assets/, when present | Application media such as images and fonts. |
.pax/ | Generated build and development files, created by the CLI. Make application changes in the source files above. |
Pax templates describe the interface. Rust holds the state and behavior behind it. Expressions inside templates connect property values to what you see on screen. You'll work with these parts together as you build an interface.
Make a first edit
Open src/lib.pax and find the LogoCard near the top. Change its width from:
width={is_compact ? 290px : 382px}
to:
width={is_compact ? 320px : 420px}
Save with the development session still running. The card becomes wider through Pax's default template hot reload. The expression chooses a compact width for smaller windows and a larger width otherwise; try resizing the browser. Restore the original values whenever you like. Rust changes normally require restarting the run, or opting into logic reload.
When something goes wrong
| Symptom | What to check |
|---|---|
cargo or pax-cli is not found | Confirm the installation finished successfully. Rust installs commands in $HOME/.cargo/bin on macOS/Linux or %USERPROFILE%\.cargo\bin on Windows. That directory must be on your shell's PATH; reopening the terminal after installation usually picks up the change. |
| A build cannot find the WebAssembly target or its standard library | Run rustup target add wasm32-unknown-unknown, then retry. |
wasm-pack is not found | Run cargo install wasm-pack --version 0.15.0, then check wasm-pack --version. |
| A compiler, linker, or system library is missing | Revisit your workstation setup. On Windows, use the PowerShell session where you loaded the MSVC environment. |
| The project destination already exists | Choose a new directory name. Keep any existing project files. |
| The browser cannot connect | Check that pax-cli run is still running and use the local URL from that session. |
For more build detail, run pax-cli run --target=web --verbose. If compilation
fails, start with the first reported error. When asking for help, include that
error, your workstation OS, and the output of pax-cli --version.
Where to go next
Continue with Template Language to learn how to describe an interface. Data Binding and Expressions explains how values flow into that interface, and Event Handling with Rust covers responding to input.
For hot reload, inspection, screenshots, and local reference, continue with Developer Workflow and Tools.
To choose another target or ship a release, see Targets, Build, and Deployment.
Further reference
The links below preserve earlier reference locations. Detailed workflow and packaging guidance lives in the linked chapters.
CLI telemetry
The first public CLI command prints the telemetry privacy notice and sends no telemetry. Later public commands enable minimal telemetry by default. See CLI Telemetry for the fields and opt-out controls.
Formatting Pax source
Use pax-cli fmt for Pax source and pax-cli fmt --check to check without
writing. See Format Pax source for
file selection, inline templates, and CI usage.
Hot reloading
Debug runs reload .pax templates by default. For Rust changes, stop and rerun
the app, or opt into logic reload on web/macOS. See
Hot reloading for the mode matrix,
target boundaries, and project configuration.
Web public files
Use a project-root public/ directory for web-only files such as robots.txt
and well-known metadata. See Web public files
for copying, live serving, reserved paths, and the distinction from app assets.
Project Metadata
Configure titles, icons, Apple identity, and packaging values in
[package.metadata.pax] in Cargo.toml. See
Project metadata for the supported
keys, target inheritance, and icon requirements.
Local release runs on iOS and iPadOS
Use pax-cli run --release --target ios or --target ipados for an optimized
simulator run. Connected-device runs also accept a device selector and Apple
development team. See Local release runs
for signing, device selection, and the boundary with App Store distribution.
CLI Telemetry
Pax uses a small amount of CLI telemetry to understand whether the tooling works and whether installations return to build applications. The first public CLI command on an installation prints a notice and sends no telemetry. Later public commands enable telemetry by default.
Telemetry contains only:
- a random installation identifier stored by the CLI;
- the CLI version;
- the host operating-system family and CPU architecture;
- the top-level command family and, when applicable, its Pax target;
- whether a finished command succeeded or failed;
- whether a
pax-cli runsession reached its target-specific ready point; and - approximate city, region, and country derived server-side from the connection IP.
The CLI does not add an IP address or location field to its telemetry payload. Like any HTTPS service, the telemetry service sees the connection IP while handling the request. It passes that address to Mixpanel only as reserved, transient geolocation input. Mixpanel derives the approximate city, region, and country and discards the IP before ingesting the event. Pax does not log or persist the raw address. The coarse location is used only in aggregate to guide product, content, outreach, and marketing investment.
A run that reaches its target-specific ready point emits only the ready
event, never a second event when it exits. Without observed readiness, a
finished run emits only its command outcome. In particular, iOS/iPadOS release
runs have no readiness channel: a clean exit is a successful command outcome,
not evidence of activation. Activation counts only ready events.
Pax does not collect source code, filenames, project paths, project names, project content, command arguments, error text, account information, locale, or hardware-derived identifiers.
Events are sent on a bounded best-effort basis by a short-lived background copy of the CLI. They are not persisted, durably queued, or retried. A telemetry failure never changes command behavior or exit status, and the foreground CLI does not wait for network delivery. The worker may finish after the command has returned to the prompt. Each request has a two-second timeout; once started, the worker has a five-second deadline for input handling, local setup and delivery. If worker launch or delivery fails, that event is silently dropped.
The worker rechecks consent immediately before sending. Ordinary commands and
telemetry status do not wait for in-flight network requests. An explicit
telemetry off disables future sends first, then waits up to 2.5 seconds for
any already-authorized requests to finish before reporting success. If that
wait times out, it reports an error but telemetry remains disabled.
Controls
Use these commands to inspect or change the persistent setting:
pax-cli telemetry status
pax-cli telemetry off
pax-cli telemetry on
pax-cli telemetry on creates a fresh random installation ID immediately but
sends no event itself. The next public command may send telemetry. Turning
telemetry off and back on therefore starts a new installation identity.
Set PAX_TELEMETRY=off or DO_NOT_TRACK=1 to disable telemetry for a process.
Telemetry is also disabled by default when CI=1 or CI=true is present.
The local setting is stored under the operating system's Pax application-state directory:
- macOS:
~/Library/Application Support/Pax/telemetry - Windows:
%LOCALAPPDATA%\Pax\telemetry - Linux:
$XDG_STATE_HOME/pax/telemetry, or~/.local/state/pax/telemetry
The random installation identifier represents this local CLI installation and is not tied to a person, account, hardware fingerprint, or project. Turning telemetry off removes that identifier. Turning it on again creates a new one immediately without sending an event.
Update checks
CLI update checking is functional and separate from telemetry. It continues when telemetry is off and never includes the installation identifier. Like any HTTPS request, the update service sees the connection IP while handling the request. Under the server contract, Pax does not retain that raw IP, derive coarse location for the update check, or turn the update request into a telemetry event. Update checks run in a background thread and never delay CLI exit; an update notice is shown only if the result is already available.
Templates and UI structure
A Pax template describes a component's interface: the elements it contains, their properties, and their connections to state and events. Reading the tree gives you its structure; reading the values on each element tells you how that structure becomes a visible interface.
This chapter starts with a small panel, then introduces the settings mechanisms you can use as an interface grows. If you haven't run a Pax project yet, Getting Started covers installation and the first run.
Reading a template
A component usually pairs a Rust type with a .pax file. For a main component
called Notes, the Rust declaration can be as small as:
#![allow(unused)] fn main() { use pax_kit::*; #[pax] #[main] #[file("lib.pax")] pub struct Notes {} }
#[pax] makes the type a Pax component, #[main] identifies the application
root, and #[file("lib.pax")] names its template. Here is that template:
<Group x=24px y=24px width=320px height=160px>
<Text x=24px y=24px width={100% - 48px} height=36px
text="Field notes"
style={font_size: 24px, fill: rgb(32, 40, 48)}
/>
<Text x=24px y=72px width={100% - 48px} height=48px
text="A place for your next idea."
style={font_size: 16px, fill: rgb(72, 80, 88)}
/>
// The background is behind the text above it.
<Rectangle width=100% height=100%
fill=rgb(246, 241, 230) corner_radius=12
/>
</Group>
Tags name components, using PascalCase: Group, Text, and Rectangle here.
The Group has an opening and closing tag because it contains other elements.
The two Text elements and the Rectangle use self-closing tags. Visible words
are supplied through the Text component's text property.
The group is the parent of these three elements, and they are siblings of one another. Each child's position and percentage dimensions are relative to its container. The group supplies a shared 320-by-160-pixel area for this panel. A template can also contain several root elements; add a container when you need them to share a layout area.
Comments such as the // line above help explain the intent of a template.
Keep them between elements or settings. Pax also accepts block comments and
HTML-style comments in those positions.
Components can use templates embedded in Rust with #[inlined(...)] as well.
Separate .pax files keep the interface easy to find and edit. See
Components and Composition for defining and
organizing reusable components.
Setting values
An attribute such as width=320px assigns a property on an element. Some
properties are common across elements, including position and size; others,
such as text or corner_radius, belong to the receiving component.
The shape of the value tells you how it is supplied:
| Form | Example | Meaning |
|---|---|---|
| Literal | text="Field notes" | Use this value directly. |
| Value with a unit | width=320px or width=100% | Express a dimension in pixels or relative to the parent. |
| Expression | width={100% - 48px} | Derive the value with PAXEL. |
| State binding | text={self.title} | Read the containing component's title property reactively. |
| Event binding | @click=self.activate | Invoke the component's activate handler when the event occurs. |
| Read/write binding | text=bind:title | Connect a supporting control's value to the component's property in both directions. |
The last three examples assume the Rust component exposes the named property
or handler. In a template expression, self refers to that containing
component. It gives a child element access to the state of the component whose
template you are reading.
Braces mark PAXEL expressions. When title changes, a Text element using
text={self.title} updates from that value. A control such as
<Textbox text=bind:title /> can also write edits back to title.
See Data Binding and Expressions for expression
syntax and State and Properties for the Rust side.
Event bindings connect the tree to application behavior. The handler's body lives in Rust; Event Handling and Rust Logic covers its signature, event data, and state updates.
Element order
Earlier siblings appear in front of later siblings. In the panel above, both text elements are in front of the rectangle. Moving the rectangle before the text would put its opaque fill over the words.
This makes a template read like a stack of layers: foreground details first, background last. It is worth checking source order whenever an element seems to have disappeared behind another.
Nesting, draw order, and spatial layout each answer a different question.
Nesting gives elements a shared container; sibling order determines which is
in front; properties such as x, y, width, and height determine their
geometry. A Group lets you place children in its area. Flow containers such
as Stacker can arrange children for you. Continue with
Layout and Responsiveness for those layout tools,
or Compositing and Effects for clipping, masks, and
native-element layering.
IDs and classes
Inline values keep a small element easy to read. As settings become reusable,
give elements a class and collect those settings in an @settings block:
<Group id=panel x=24px y=24px>
<Text class="heading" x=24px y=24px width={100% - 48px}
height=36px text="Field notes" />
<Text class="body" x=24px y=72px width={100% - 48px}
height=48px text="A place for your next idea." />
<Rectangle class="surface" width=100% height=100% />
</Group>
@settings {
#panel {
width: 320px
height: 160px
}
.heading {
style: {
font_size: 24px
fill: rgb(32, 40, 48)
}
}
.body {
style: {
font_size: 16px
fill: rgb(72, 80, 88)
}
}
.surface {
fill: rgb(246, 241, 230)
corner_radius: 12
}
}
This produces the same panel. Tags use = for assignments; the settings block
uses :. The nested style object groups properties of the text style.
Use an id to address an individual node within the template, and a class for
settings that several elements can share. #panel selects id=panel;
.heading selects elements with class="heading". The supported selectors
inside @settings are these ID and class forms.
Settings belong to the component's template. A class on a child component's tag can configure that child instance; the child's internal template has its own settings. Explicit sharing is covered in Imported settings.
Multiple and reactive classes
A single class is a string. Multiple classes use one ordered string list:
<Rectangle class=["surface", "selected"] />
Within the component's local settings, matching classes apply in the order listed on the element. Later classes can override earlier ones, matching ID settings apply after those classes, and inline values apply last. If several blocks use the same selector, their source order decides which comes later.
For example, an inline fill=WHITE wins over a fill supplied by .surface,
.selected, or a local ID selector. Put a value inline when it should remain
specific to that element; put it in a class when another class or a theme should
be able to vary it.
Class bindings can be reactive too. If the containing component exposes a
boolean selected property, it can choose the class list:
<Rectangle class={self.selected ? ["surface", "selected"] : "surface"} />
An expression may return a string or a list of strings. It can read a Rust
component property of type Property<String> or Property<Vec<String>>.
There are a few precise rules worth keeping nearby:
- An empty string or empty list means no classes. A class may have no matching selector.
- A string names one class and is never split on whitespace. Write
["surface", "selected"]for two classes. - Duplicate names keep their final position in the list.
- Class names start with an ASCII letter or underscore, followed by ASCII letters, digits, underscores, or hyphens. Invalid names and non-string values are ignored with a runtime warning.
- An element has one
classattribute. The formatter writes a one-item literal list as a single string.
class selects settings; it cannot itself be assigned inside a selector,
settings, or timeline block. Give every inline property a single assignment.
Conditional settings
Settings can respond to a condition while keeping the same elements in the tree. For example, this panel has a fixed width in a wide viewport and leaves a 24-pixel margin on each side in a narrow one:
<Group class="panel" x=24px y=24px height=160px>
<Rectangle class="surface" width=100% height=100% />
</Group>
@settings {
.panel {
width: 320px
}
.surface {
fill: rgb(246, 241, 230)
corner_radius: 12
}
if $viewport.width < 600 {
.panel {
width: {100% - 48px}
}
}
}
The condition is a PAXEL expression that evaluates to a boolean. Pax updates
the affected settings when its inputs change. Here, the conditional width
overrides the earlier .panel width while the viewport is under 600 pixels;
the earlier width applies again when the condition becomes false.
Use else if and else for additional branches. Conditions can also use
component state or built-in globals such as $ios, $macos, and $landscape.
The viewport describes the application window; percentages in the assigned
width still resolve against the element's parent. See
Layout and Responsiveness for choosing responsive
rules for nested components.
Place these conditions around selector blocks inside @settings. They may
contain nested conditions. Inside an individual selector, use a PAXEL expression
for a conditional property value. An if among the template's elements instead
controls which elements exist; Conditional content
explains that structural form.
Imported settings
ImportSettings lets a component use settings provided by other components.
This is useful for sharing a palette, typography, or other coordinated choices.
The provider subtree supplies settings without drawing its own interface.
For example, declare a provider alongside the main component in Rust:
#![allow(unused)] fn main() { #[pax] #[file("paper_theme.pax")] pub struct PaperTheme {} }
Its paper_theme.pax file can supply the panel's surface settings:
<Group />
@settings {
.surface {
fill: rgb(232, 240, 226)
corner_radius: 18
}
}
Add the provider to the panel's template, keeping the visible elements as
siblings of ImportSettings:
<ImportSettings>
<PaperTheme />
</ImportSettings>
<Group x=24px y=24px width=320px height=160px>
<Text x=24px y=24px width={100% - 48px} height=36px
text="Field notes"
style={font_size: 24px, fill: rgb(32, 40, 48)}
/>
<Rectangle class="surface" width=100% height=100% />
</Group>
The rectangle receives .surface from PaperTheme. The import applies to
elements authored in the containing component's template. A child component
manages imports for its own internal template.
For ordinary settings, the layers are applied in this order:
- The containing component's local settings.
- Imported providers, in provider order.
- Inline values on the element.
Later layers override earlier ones for the same property. Within each provider,
the same class-list and ID ordering rules apply. A class in an imported provider
can therefore override a local ID setting; layer order is considered before
selector order. When providers are direct siblings inside one ImportSettings,
their order in the template is their layer order.
The provider can expose reactive properties of its own. Expressions in its
settings use that provider's self and state, so a shared palette can change
reactively. See Drawing and Styling for visual design
choices and PAXEL's $base for building on
an earlier property value. Timelines add animation-specific behavior covered
in Animation and Motion.
Read more
Templates also support structural if and for blocks, child projection with
slot(...), and motion with @timeline. Continue with
Components and Composition
and Animation and Motion as you encounter those needs.
For working with values, Data Binding and Expressions
is the next step. To keep source formatting consistent as you edit, use
pax-cli fmt.
PAXEL and reactive values
A label might show a name, a bar might show progress, and a color might signal
that something is ready. PAXEL connects those visible values to the state they
describe. It is Pax's expression language, written inside {...} in templates.
Think of each expression as a spreadsheet formula: it describes how to obtain a value from its inputs. Rust supplies application state and changes it in response to events; the formulas let the interface follow along.
This chapter builds on the panel from Templates. It covers the values a template can derive. State and Properties explains how to manage those values in Rust, including computed properties and subscriptions.
Reactive bindings
Start with a literal, then replace it with a property read:
<Text text="Field notes" />
<Text text={self.title} />
The first element always displays the same words. The second reads title
from the component that owns the template. That component exposes the value
through a Rust Property<T> field. Here is a complete declaration with a title
and a progress value:
#![allow(unused)] fn main() { use pax_kit::*; #[pax] #[main] #[file("lib.pax")] pub struct Notes { pub title: Property<String>, pub progress: Property<f64>, } impl Notes { pub fn on_mount(&mut self, _ctx: &NodeContext) { self.title.set("Field notes".to_string()); self.progress.set(0.25); } } }
Pax calls on_mount when the component mounts. These initial values give the
template something to display. In lib.pax, the same progress value can drive
several properties:
<Group x=24px y=24px width=320px height=220px>
<Text x=24px y=24px width={100% - 48px} height=36px
text={self.title}
style={font_size: 24px, fill: rgb(32, 40, 48)}
/>
<Slider x=24px y=76px width={100% - 48px} height=24px
value=bind:progress min=0.0 max=1.0 step=0.25
/>
<Text x=24px y=116px width={100% - 48px} height=28px
text={"Progress: " + (self.progress * 100) + "%"}
style={font_size: 16px, fill: rgb(72, 80, 88)}
/>
<Group x=24px y=160px width={100% - 48px} height=12px>
<Rectangle width={(self.progress * 100)%} height=100%
fill={self.progress >= 0.5 ? rgb(55, 120, 90) : rgb(75, 100, 160)}
corner_radius=6
/>
<Rectangle width=100% height=100%
fill=rgb(220, 216, 206) corner_radius=6
/>
</Group>
<Rectangle width=100% height=100%
fill=rgb(246, 241, 230) corner_radius=12
/>
</Group>
Dragging the slider writes to progress. The label reads it to form a string;
the foreground rectangle reads it to choose a percentage width and a color.
At 0.25, the bar fills a quarter of its container. At 0.5, it fills half
and turns green.
Each formula reading progress is a derived value with a dependency on that
property. A write marks those derived values for reevaluation. Pax computes
them when they are next needed and caches their results between changes.
There is no separate redraw call in this example.
Reading versus writing
An expression such as text={self.title} reads state. A supporting control can
also write back through bind::
<Textbox text=bind:title />
Typing updates the same title property that the heading reads. bind: names
a property to share with the control; it does not accept a formula for a
derived result. Use {...} for labels, dimensions, colors, and other calculated
values. See Accessibility and Native Controls for control
behavior, and Events and Rust for updates that need
application logic.
In this panel, self.title and title read the same property. This chapter
uses the explicit self form when referring to component state.
Literals
The receiving property supplies the expected type. A Text needs a string for
text; a Rectangle needs a fill for fill and a size for width. Pax converts
compatible values to that type. A value with the wrong shape or type is an
error.
| Kind | Examples |
|---|---|
| Number | 24, 0.25 |
| Boolean | true, false |
| String | "Field notes" |
| Size | 24px, 50% |
| Rotation | 15deg, 0.5rad |
| Time | 250ms, 1s, 5f |
| Color | BLUE, rgb(32, 40, 48), rgba(32, 40, 48, 128) |
| List | [120px, None, 30%] |
| Contextual object | {font_size: 16px, fill: BLUE} |
| Empty option | None |
Some literals have a property-specific meaning. For example, the entries in
a Stacker's sizes list describe its cells, while entries in a rectangle's
corner_radius list describe its corners. The receiving type defines those
positions; see the shape reference below.
Units in formulas
Units remain useful inside expressions:
<Rectangle width={100% - 48px} />
<Rectangle width={(self.progress * 100)%} />
The first width leaves 48 pixels out of the available parent width. The second turns a numeric fraction into a percentage. Layout determines what a dimension is relative to; Layout and Responsiveness develops that relationship in more detail.
Write a unit immediately after its number or parenthesized expression:
24px and (self.progress * 100)%. A space between the value and the unit
breaks that form. % means percent; the modulo operator is %%.
Time suffixes follow the same pattern: (100 + self.delay)ms produces a
duration if delay is numeric. Animation and Motion
covers durations, frame counts, and clocks.
Choosing values
A ternary selects a value using a boolean condition:
<Text text={self.progress >= 1.0 ? "Ready" : "In progress"} />
The form is condition ? when_true : when_false. Pax evaluates the chosen
branch. Both alternatives should produce values compatible with the receiving
property. A ternary changes a value on an existing element; use
conditional content when the condition should determine
which elements exist.
For optional data, ?? supplies a fallback. If the component declares
maybe_title: Property<Option<String>>, its template can use:
<Text text={self.maybe_title ?? "Untitled"} />
An option contains either Some(value) or None. ?? unwraps a present
value, and evaluates its right side only when the left side is None. A
non-option value passes through unchanged: false, zero, and an empty string
all remain valid values. The fallback does not catch an error in the expression
on its left.
When assigning to an Option<T> property, an ordinary compatible value can be
lifted into the option automatically. Write None for the empty case; there is
no need to wrap a present template literal in Some(...).
Structured values
Use . to read a field and square brackets to read a zero-based list entry.
For example, if items is a nonempty Property<Vec<String>>, self.items[0]
reads its first string. The index must be within the list's bounds. Update
application data through the owning Rust property so its dependent values can
be invalidated.
A list can itself be derived. Given first_size: Property<Size>, this Stacker
uses it for the first cell and leaves the second flexible:
<Stacker sizes={[self.first_size, None]}>
<Text text="First cell" />
<Text text="Flexible cell" />
</Stacker>
The outer braces make the whole list an expression. Use this form when list
entries read state; a literal list such as [120px, None] is convenient for
fixed values.
Contextual objects can hold individual bindings. With
heading_size: Property<Size>, a text style can keep its color fixed while
reading the font size:
<Text text="Field notes" style={
font_size: {self.heading_size}
fill: rgb(32, 40, 48)
} />
Here the outer braces describe the style's fields, and the braces around
self.heading_size mark that field's expression. An expression can also
produce a whole object; the receiving property still determines which fields
and types are meaningful.
Loops
A range such as 0..3 includes 0, 1, and 2. Its upper bound is exclusive.
Ranges often provide the source for a template for:
for i in 0..3 {
<Text y={(i * 24)px} text={"Item " + i} />
}
The template repeats the element; PAXEL supplies its values. Continue with Data-driven components for iteration over data, keys, and item identity.
Function calls
Built-in functions can express a calculation more clearly than a longer
formula. Math::min and Math::max choose between two values; Math::len
returns a list's length. For example, a numeric desired_width can become a
width with an 80-pixel minimum:
<Rectangle width={(Math::max(80, self.desired_width))px} />
Function calls use a registered Type::function(...) name. Enum constructors
also use path syntax, such as StackerDirection::Horizontal. PAXEL's call
syntax reaches the functions and constructors exposed to it; arbitrary Rust
methods are not automatically available.
A small Rust helper
To give the panel a reusable progress label, add #[has_helpers] alongside the
attributes on the existing Notes declaration. Then add a separate helper
implementation:
#![allow(unused)] fn main() { #[helpers] impl Notes { pub fn progress_label(progress: f64) -> String { format!("{:.0}% complete", progress * 100.0) } } }
Call it with the changing value as an explicit argument:
<Text text={Notes::progress_label(self.progress)} />
#[helpers] exposes public associated functions in that implementation; they
have no self receiver. #[has_helpers] tells the Pax type declaration to use
that helper implementation. Keep lifecycle methods such as on_mount in the
ordinary impl block shown earlier.
The formula can track self.progress because it appears in the arguments.
Hidden reads of external state inside a helper cannot supply that connection.
Helpers should return a value without changing application state, reading
files, or performing other side effects. Rust event handlers and subscriptions
provide those capabilities. See State and Properties
when a derived value needs to be shared beyond one template.
Built-in globals
Built-in values use a $ prefix. They describe the running app and its
environment:
| Value | What it provides |
|---|---|
$viewport | Scene width and height in logical pixels; major, minor, aspect, landscape, portrait, and square. |
$target | Boolean platform and operating-system facts, such as web, native, macos, ios, iphone, and ipad. |
$frames, $millis | Runtime frame count and elapsed time in milliseconds. |
$gyro, $accel | Device orientation and acceleration readings, where supplied by the target and permitted by the user. |
Viewport orientation and shape fields also have short aliases, including
$landscape, $portrait, $square, $major, $minor, and $aspect:
<Group width={$landscape ? 50% : 100%} />
<Rectangle width={($viewport.width / 2)px} />
The viewport dimensions are numbers. Attach a unit when using them to produce a size. A parent-relative percentage is often more useful for nested content.
Each $target field likewise has an alias: $web means $target.web, for
example. Platform and operating-system facts can overlap: an app in a browser
on macOS can have both $web and $macos set. The remaining fields are
android, windows, linux, mobile, and desktop. These describe runtime
facts, including browser hosts; they do not extend the supported build targets
beyond web, macOS, iOS, and iPadOS.
The sensor values expose x, y, and z. Orientation is in degrees and
acceleration is in meters per second squared; web acceleration includes gravity
when that reading is available. Mobile WebKit requires a user gesture before
sensors can stream. A web app can request access by invoking
window.paxRequestDeviceSensorPermissions() from an app-owned JavaScript
button handler. This is a web integration call, separate from PAXEL. See
Accessibility and Native Controls for input behavior and
Animation and Motion for using clocks.
$base
$base refers to the earlier value of the same property while Pax resolves
its settings. It lets a later assignment build on what an earlier one supplied:
<Rectangle class="card" width={$base + 24px} height=80px />
@settings {
.card {
width: 240px
}
}
The class supplies a width of 240 pixels. The inline expression adds 24, so this rectangle is 264 pixels wide. If the class width changes, the inline formula continues to build on it.
$base is contextual to the property being resolved. It does not read a parent
element's property or the previous animation frame. See
Templates for ordinary settings
layers and Animation and Motion for timeline-relative
values.
Operators
This is a syntax lookup, grouped by purpose. Parentheses make the intended grouping explicit in formulas that mix different operations.
| Purpose | Operators | Example |
|---|---|---|
| Fallback | ?? | maybe_title ?? "Untitled" |
| Choice | ? : | selected ? BLUE : GRAY |
| Boolean | !, &&, || | enabled && !disabled |
| Equality | ==, != | mode != "hidden" |
| Comparison | <, <=, >, >= | progress >= 0.5 |
| Arithmetic | +, -, *, /, %%, ^ | (index + 1) * 24 |
| Range | .. | 0..count |
| Access | ., [...] | user.name, items[0] |
| Grouping and units | (...) with optional suffix | (width + 12)px |
+ also joins strings and displayable values, as in the panel's progress
label. ^ is exponentiation. The unit suffixes are px, %, deg, rad,
ms, s, and f.
Shape value reference
The following forms are useful when assigning structured values to drawing properties. Their syntax and visual use now live together in Drawing and Styling.
Corner radii
See Corner radii for the one-to-four-value list, its per-corner mapping, and the named-object longhand.
Gradients
See Gradients for @gradient stops, [x, y]
points, alpha, linear defaults, and current radial/backend limits.
Timeline-relative values
See Relative values and property ownership
for using $base in timeline keyframes, setting a base layout value, and
understanding when an inline assignment overrides an ordinary timeline track.
Read more
State and Properties is the next step for the Rust side of reactivity. Events and Rust connects user actions to state changes. For a larger template, continue with Components and Composition, including conditional content, lists, and keyed identity.
State and Properties
A progress value can feed a label, a bar, and a color. In PAXEL, we described those relationships with template formulas. Here we turn to the Rust side: where the value lives, how an action changes it, and how to build relationships between properties in Rust.
Pax represents reactive state with Property<T>, where T is the value's
Rust type. A property can hold a value your application writes, or compute a
value from other properties. Both participate in the same reactive graph as
template expressions.
This chapter continues the small Field notes panel from Templates. It includes the component declaration and template needed to try the first example in a project from Getting Started.
State in a component
The panel has a title and a numeric progress value. Declare them as fields on the Rust component that owns its template:
#![allow(unused)] fn main() { use pax_kit::*; #[pax] #[main] #[file("lib.pax")] pub struct Notes { pub title: Property<String>, pub progress: Property<f64>, } }
Rust accesses these values through methods such as self.progress.get().
The template reads the value directly with {self.progress}. PAXEL connects
that read to the property so subsequent writes can update the interface.
Use a property for state that other reactive values need to observe. Ordinary
Rust data is useful for calculations and data structures, but changing a
plain field alone does not notify the graph. Fields on a #[pax] type also
participate in generated serialization and value-conversion code; they must
meet that generated code's type requirements.
Initial values
By default, #[pax] derives Rust's Default trait for the component.
Property<T>::default() creates a property containing T::default(): the
title starts as an empty string and progress starts at zero.
To give this panel its initial content, add #[custom(Default)] alongside
the existing attributes on Notes, then supply the implementation:
#![allow(unused)] fn main() { impl Default for Notes { fn default() -> Self { Self { title: Property::new("Field notes".to_string()), progress: Property::new(0.25), } } } }
#[custom(Default)] tells the macro to use your implementation in place of
its generated one. Each call creates properties for that component instance.
Property::new also works outside a component when you need a local reactive
value.
The preceding PAXEL example set these same values in on_mount. With the
custom default above, remove those initialization writes. Mount remains a
useful place for setup that needs NodeContext or the component's established
property connections, as the computed-property example below will show.
See Events and Rust for lifecycle behavior and
Components and Composition for values supplied
by a parent component.
Reading and updating state
Give the panel a button that advances progress by a quarter, up to one. Add
this handler to Notes:
#![allow(unused)] fn main() { impl Notes { pub fn advance(&mut self, _ctx: &NodeContext, _event: Event<ButtonClick>) { let next = (self.progress.get() + 0.25).min(1.0); self.progress.set(next); } } }
In lib.pax, bind the button to the handler and read progress in the label
and bar:
<Group x=24px y=24px width=320px height=220px>
<Text x=24px y=24px width={100% - 48px} height=36px
text={self.title}
style={font_size: 24px, fill: rgb(32, 40, 48)}
/>
<Button x=24px y=76px width=160px height=28px
label="Advance" @button_click=self.advance
/>
<Text x=24px y=116px width={100% - 48px} height=28px
text={"Progress: " + (self.progress * 100) + "%"}
style={font_size: 16px, fill: rgb(72, 80, 88)}
/>
<Group x=24px y=160px width={100% - 48px} height=12px>
<Rectangle width={(self.progress * 100)%} height=100%
fill=rgb(55, 120, 90) corner_radius=6
/>
<Rectangle width=100% height=100%
fill=rgb(220, 216, 206) corner_radius=6
/>
</Group>
<Rectangle width=100% height=100%
fill=rgb(246, 241, 230) corner_radius=12
/>
</Group>
The handler reads the current value, calculates the next one, and writes it
with set. The label and width then follow the new progress. Event wiring is
covered in Events and Rust; the state operation
itself is the same whether an update begins with a button, a data-loading
result, or another application action.
Collections and value snapshots
get() returns a clone of the stored value. For a Vec<String>, you can edit
that copy and write it back, or use update to perform both steps:
#![allow(unused)] fn main() { let entries = Property::new(vec!["Sketch the header".to_string()]); let mut draft = entries.get(); draft.push("Choose a palette".to_string()); assert_eq!(entries.get().len(), 1); entries.set(draft); assert_eq!(entries.get().len(), 2); entries.update(|items| items.push("Add an interaction".to_string())); assert_eq!(entries.get().len(), 3); }
The first push changes the local vector. set publishes that vector through
the property. update is a convenient get–mutate–set operation; it also clones
the value before calling your closure. Use the same pattern for a property
holding a struct whose fields you want to change together.
The access methods have these roles:
| Method | Behavior |
|---|---|
get() | Bring a dirty property up to date, then clone and return its value. |
read(f) | Bring it up to date, then lend its value to f without cloning it. |
set(value) | Store a value and invalidate downstream dependents. |
set_if_neq(value) | Set only when the value differs from the current one; return whether a write occurred. |
update(f) | Clone the current value, let f mutate that copy, then store it and invalidate dependents. |
set and update invalidate dependents even if the resulting value is equal
to the previous one. Use set_if_neq when unchanged writes are common and
equality is inexpensive; it requires T: PartialEq. The button above could
use it to avoid another invalidation when progress is already at one.
For an inspection that does not need a copy, entries.read(|items| items.len())
returns the count without cloning the vector. Keep a read closure focused
on its borrowed value. Reading or writing the same property again inside
that closure can panic, including when another property evaluation leads
back to it.
Property handles
Cloning a Property<T> gives you another handle to the same graph node. A
write through either handle changes the value both observe:
#![allow(unused)] fn main() { let progress = Property::new(0.25_f64); let progress_handle = progress.clone(); let earlier_value = progress.get(); progress_handle.set(0.5); assert_eq!(progress.get(), 0.5); assert_eq!(earlier_value, 0.25); }
This is useful when a closure needs access to a property after the method that created it returns. Move a cloned handle into the closure; later reads still obtain the property's current value.
The depth of a value copy follows T's own Clone implementation. If a value
contains other property handles or shared pointers, cloning that value can
still share the objects inside it. In particular, cloning an entire component
does not produce independent copies of its property values.
To change an existing component field's value, call set or update. An
assignment such as self.progress = Property::new(0.5) gives the field a new
handle; any templates or closures holding the previous handle keep their
old connection. The next section introduces replace_with for changing how
an existing field is computed while keeping its graph identity.
Shared handles are also useful across components. Continue with Components and Composition for ownership, parent/child bindings, and scoped state. Property handles belong to the runtime's thread-local graph; use an appropriate application messaging boundary when work runs on another thread.
Computed properties
A computed property derives its value from other properties. A PAXEL formula
already does this for a template. Rust's Property::computed is useful when
the derived value should be available to Rust code as well, or when you want
to assemble the dependency relationship in Rust.
For example, the panel can expose its formatted progress label as a property.
Add pub progress_label: Property<String> to Notes and
progress_label: Property::default() to its custom default. Then add this
mount method:
#![allow(unused)] fn main() { impl Notes { pub fn on_mount(&mut self, _ctx: &NodeContext) { let progress = self.progress.clone(); let dependencies = [progress.untyped()]; self.progress_label.replace_with(Property::computed( move || format!("{:.0}% complete", progress.get() * 100.0), &dependencies, )); } } }
Replace the panel's progress text element with:
<Text x=24px y=116px width={100% - 48px} height=28px
text={self.progress_label}
style={font_size: 16px, fill: rgb(72, 80, 88)}
/>
The initial label reads “25% complete.” Pressing Advance changes the source property and the label becomes “50% complete.” The bar still reads progress through its PAXEL formula.
Property::computed takes an evaluator and an explicit dependency list.
.untyped() provides a handle suitable for that list, allowing properties
with different value types to be dependencies of one computation. Include
every input whose changes should trigger reevaluation. A .get() inside a
Rust evaluator does not automatically register a dependency.
replace_with installs the new evaluator and dependencies on the existing
progress_label field. The field keeps the outgoing connections already
established by its template and any other consumers. For a new local
property, you can use the handle returned by Property::computed directly.
Change derived state by updating its source inputs. Writing to a computed
property with set changes its stored value but leaves the evaluator in
place; a later dependency change can compute the value again. Keep the
dependency graph acyclic, and avoid an evaluator that reads the very field
it is replacing, directly or through another computed property.
Computed evaluators should be deterministic and free of side effects. Their execution follows demand for a value. Use a subscription for work that must respond to a change independently of a value being read.
Subscriptions and effects
A subscription runs a callback in response to reactive dependencies. Use it for a bounded side effect, such as notifying an application service that a selection changed. A pure derived value is usually simpler as a computed property or PAXEL formula.
For a small observable example, log the panel's progress. Rename _ctx to
ctx in the mount method above and add the following after its computed
property setup:
#![allow(unused)] fn main() { let progress = self.progress.clone(); let dependencies = [progress.untyped()]; ctx.subscribe(&dependencies, move || { log::info!("Progress observed: {}", progress.get()); }); }
On the web, append ?pax_log=info to the running app's URL and reload to see
these messages in the browser console. Informational logs are hidden at the
default logging level.
The callback is scheduled once after registration to observe the initial
state. It is then scheduled when a dependency dirties. subscribe does not
call it immediately inside the registration call, and several writes before
the reactive work is drained can produce one callback observing the latest
value. A subscription is therefore useful for observing current state; it
does not provide a record of every intermediate write.
NodeContext retains subscriptions for its node and clears them when the
node unmounts. ctx.clear_subscriptions() removes all subscriptions registered
on that node. subscribe returns no individual cancellation handle.
Callbacks run synchronously as Pax drains reactive work during runtime updates. They may set another property and enqueue further work. Keep them short, move blocking I/O outside the callback, and avoid effects that continually dirty one another. For actions that should happen specifically because a user pressed a button, put the action in the event handler; see Events and Rust.
How updates travel
The property graph records which values depend on which inputs. The panel now contains relationships like these:
progress ----> bar width (PAXEL)
|
+--------> progress_label (Rust) ----> text (PAXEL)
Setting progress marks its ordinary computed dependents dirty: their cached values may be out of date. This invalidation travels through those dependents before their new values are calculated.
A later get or read, including a read needed to update the interface,
recomputes the required dirty values. As each evaluator reads its inputs,
those inputs are brought up to date too. Repeated reads of a clean computed
property use its cached value. Multiple writes can coalesce before a read,
and an ordinary computed value with no observers need not run just because
an input changed.
This is eager invalidation with on-demand evaluation. Subscriptions are explicit observers: the runtime schedules them when they become dirty. Propagation cutoffs, described next, also schedule work so they can decide whether a change should travel farther through the graph.
Propagation cutoffs
Cutoffs are an advanced tool for a graph whose frequently changing inputs produce comparatively stable outputs. The ordinary properties above are enough for the panel; consider a cutoff when measurement points to expensive downstream work that often produces the same result.
Ordinary invalidation travels through a computed property before Pax knows
whether its output has changed. Property::computed_with_cutoff creates a
boundary: when an input changes, Pax evaluates this property before deciding
whether to invalidate its downstream dependents.
For example, a pointer position might select one bucket for every 20 units:
#![allow(unused)] fn main() { let pointer_x = Property::new(0.0_f64); let pointer_x_for_bucket = pointer_x.clone(); let bucket = Property::computed_with_cutoff( move || (pointer_x_for_bucket.get() / 20.0).floor() as i64, &[pointer_x.untyped()], |last_accepted, candidate| last_accepted == candidate, ); }
Moving from 2 to 3 leaves the bucket at zero. Moving to 21 changes it to one. The predicate compares the last accepted output with the new candidate:
| Return value | Result |
|---|---|
true | Discard the candidate, retain the last accepted value, and stop invalidation at the cutoff. |
false | Accept the candidate and invalidate downstream dependents. |
The first evaluation is always accepted because there is no prior accepted value to compare. After a candidate is suppressed, the next comparison still uses the last accepted value. Equality is common; approximate or domain-specific predicates are also possible, with that same retention behavior.
Cutoffs settle as part of the runtime's synchronous reactive work, before
queued effects. A direct get or read of a dirty cutoff can settle it
earlier. The cutoff still evaluates its own function; the saved work is the
invalidation and reevaluation beyond it when the output is equivalent.
Use a cutoff where inputs change frequently, the result often remains
equivalent, and downstream work is materially more expensive than evaluating
the cutoff and its predicate. Keep both callbacks inexpensive and free of
side effects. computed_with_cutoff_and_name adds a diagnostic name; the
other named property constructors can also help identify a large graph's
values during diagnosis.
Choosing a pattern
| Need | Start with |
|---|---|
| A value derived for a template | A PAXEL formula; the compiler supplies its dependency edges. |
| Application state changed by an action | A property written by the Rust handler. |
| Derived reactive state used from Rust | Property::computed, with every dependency listed explicitly. |
| A short side effect observing state | NodeContext::subscribe, with its node-scoped lifetime. |
| Fewer unchanged writes | set_if_neq, when equality is inexpensive. |
| A stable output between a busy input and expensive consumers | A measured use of computed_with_cutoff. |
For the complete method surface and value-type requirements, see the properties API. Continue with Events and Rust to connect more kinds of actions to state, Components and Composition to organize shared state, or Animation and Motion to change values over time.
Events and Rust
A template describes how an interface responds to its state. An event handler gives the user a way to change that state: advance a task, edit a title, choose a destination, or move something across the screen.
This chapter continues the Field notes panel from
Properties. We will look
closely at its button handler, add a title editor and a keyboard shortcut,
then explore lifecycle and custom interactions. The examples use the same
Notes component and its title and progress properties; the complete
starting declaration and template are in Properties. Keep use pax_kit::*;
in the Rust file.
Space Game combines keyboard input with a running simulation. Use w, a,
s, and d to move and Space to fire. In an embed, first click the game area
so keyboard input reaches its document. After game-over, choose Play Again
below the score to start a new round.
Its source combines key state with
tick-driven updates; the frame-based movement is specific to this example.
Connect an action to Rust
The panel's button names the method to call when it is activated:
<Button x=24px y=76px width=160px height=28px
label="Advance" @button_click=self.advance
/>
The corresponding method lives in impl Notes:
#![allow(unused)] fn main() { impl Notes { pub fn advance(&mut self, _ctx: &NodeContext, _event: Event<ButtonClick>) { let next = (self.progress.get() + 0.25).min(1.0); self.progress.set(next); } } }
The binding @button_click=self.advance connects the Button's activation
event to a public Rust method. The method receives three things:
&mut selfgives it mutable access to theNotesinstance that owns this template.&NodeContextdescribes the node handling the event and provides runtime information and operations, such as its bounds and navigation.Event<ButtonClick>carries the event. This particular payload has no fields; the activation itself is enough to advance progress.
The leading underscores in _ctx and _event are Rust's convention for
arguments that this method does not use. Other handlers can name them ctx
and event and read their contents.
The handler calculates a new value and writes it to the property. The label and progress bar already read that property, so their bindings pick up the change. See Properties for the state operations and PAXEL for the formulas that consume them.
Buttons and custom activation
Use @button_click with the native Button control. For custom content, such
as a card or drawing, @click and @tap provide general activation handlers.
Both receive Event<Click>, which includes position, button, and modifier
information through event.mouse.
Either binding on its own receives mouse clicks and single-touch taps. If
both are present on the same node, mouse input selects @click and touch
input selects @tap. This lets a custom surface share an action across input
types, while still allowing separate actions where needed.
A touch activation occurs at release, after TouchEnd, when the gesture
qualifies as a tap. For immediate pressed feedback or dragging, use the
lower-level mouse and touch events described under
Events and coordinates.
Use event data
An event's type tells you what data the handler receives. These are common starting points:
| Binding | Handler argument | Useful data |
|---|---|---|
@button_click | Event<ButtonClick> | Activation of a native Button |
@click, @tap | Event<Click> | event.mouse.x, .y, .button, .modifiers |
@mouse_move | Event<MouseMove> | Pointer position through event.mouse |
@touch_start, @touch_move, @touch_end, @touch_cancel | Event<TouchStart>, Event<TouchMove>, Event<TouchEnd>, Event<TouchCancel> | event.touches, including identifiers and positions |
@key_down, @key_up | Event<KeyDown>, Event<KeyUp> | event.keyboard.key, .modifiers, .is_repeat |
@textbox_input, @textbox_change | Event<TextboxInput>, Event<TextboxChange> | event.text |
@checkbox_change | Event<CheckboxChange> | event.checked |
@slider_change | Event<SliderChange> | event.value |
Event<T> exposes its payload fields directly, so event.text is a
convenient way to read event.args.text. The
event API reference lists the complete
payloads, including wheel, drop, and other input events. Sensor availability
and native control behavior also depend on the target.
To edit the panel's title, add this Textbox after the panel's outer Group
in lib.pax:
<Textbox x=24px y=260px width=320px height=32px
text={self.title} @textbox_input=self.rename
/>
Add its handler to the Rust file:
#![allow(unused)] fn main() { impl Notes { pub fn rename(&mut self, _ctx: &NodeContext, event: Event<TextboxInput>) { self.title.set(event.text.clone()); } } }
As the user types, the handler copies the event's text into title, and the
panel's heading follows. The template supplies the current title to the
Textbox; the handler makes the application's response to edits explicit.
On web, @textbox_input follows the browser's input event, while
@textbox_change follows its change event, used for committed edits. Choose
input for live feedback; choose change when the action should wait for a
commit. Exact commit timing belongs to the platform control, so test it on
the targets you ship. Forms, two-way bindings, and focus are covered in
Native Controls.
Choose the binding's scope
An inline binding belongs to the element where it is written. In the Advance
example, the Button is the receiving node, and self is the containing
Notes component. Consequently, ctx.bounds_self describes the Button's
bounds, even though self.progress is a field on Notes.
A binding in the component's @settings block runs at component scope. For
example, to handle button activations that reach Notes, move the binding off
the Button and into the template's settings:
@settings {
@button_click: advance
}
Here both self and the receiving context refer to Notes. Activations from
its buttons can reach this handler through event propagation. If you try this
alternative, remove @button_click=self.advance from the Button: keeping both
bindings calls advance at both places and increments twice.
The same distinction applies when you use a child component. A handler in the child's own settings acts on that child; an inline binding on the child's invocation acts on the containing component. See Components and Composition for the component boundary and Event delivery for propagation.
Do application work
Handlers are ordinary Rust methods. They can validate input, call helpers, update several properties, and invoke application services. Keep reusable work in a helper when more than one interaction needs it.
For example, let the right arrow key advance the panel as well as the button.
Replace the original impl Notes containing advance with:
#![allow(unused)] fn main() { impl Notes { fn advance_progress(&mut self) { let next = (self.progress.get() + 0.25).min(1.0); self.progress.set(next); } pub fn advance(&mut self, _ctx: &NodeContext, _event: Event<ButtonClick>) { self.advance_progress(); } pub fn key_down(&mut self, _ctx: &NodeContext, event: Event<KeyDown>) { if event.keyboard.key == "ArrowRight" && !event.keyboard.is_repeat { self.advance_progress(); event.prevent_default(); } } } }
Keep rename and any other methods in their existing impl blocks. Add the
keyboard binding to the template's @settings block, or create that block if
there is none:
@settings {
@key_down: key_down
}
The is_repeat check limits this action to the initial key press. On web,
try it while focus is outside the Textbox and other native controls. The
keyboard dispatch and default-prevention boundaries are explained below.
Pax calls event handlers synchronously. Keep their work short enough for
the interface to remain responsive. For network requests or lengthy work,
use an integration appropriate to your target, then publish the result into
application state on the runtime's thread. Property handles belong to a
thread-local reactive graph; they are not a cross-thread messaging API.
Declaring a handler async fn does not give Pax an executor to run it.
A loading interface can expose a loading flag, a result, and an error as properties. Its template can then describe those states while the application layer manages the request. A complete asynchronous integration is beyond this chapter. For other common actions, read Routing for navigation and Motion for easing and timeline control.
Work with component lifecycle
Some work follows the lifetime of a component rather than an input event. Pax recognizes public lifecycle methods on its Rust implementation:
| Method | When it runs | Typical use |
|---|---|---|
on_mount | When the component mounts | Install computations or subscriptions that need established bindings or context |
on_tick | On runtime ticks while the component is active | Advance an application simulation or other time-driven logic |
on_pre_render | After tick handlers, in the pre-render phase | Synchronize work that must follow that tick's updates |
on_unmount | When the component is removed from the mounted tree | Release application-owned registrations or finish mounted work |
Lifecycle methods take &mut self and &NodeContext, without an Event
argument. For a small way to observe the panel's lifetime, add:
#![allow(unused)] fn main() { impl Notes { pub fn on_mount(&mut self, _ctx: &NodeContext) { log::info!("Notes mounted"); } pub fn on_unmount(&mut self, _ctx: &NodeContext) { log::info!("Notes unmounted"); } } }
On web, open the app with ?pax_log=info to see these messages in the browser
console. The unmount message appears when Pax removes the component from the
tree, for example through a template conditional. It is not a guarantee of
cleanup on browser or process termination.
If Notes already has an on_mount, add the new work to that method. The
computed-property example uses
mount to establish a derived label. Ordinary initial values can live in
Default; mount is useful for setup that needs the mounted context or
property connections. Do not assume that all children have finished mounting
or measuring when the parent's mount handler runs.
The short names mount, tick, pre_render, and unmount are also recognized.
When both forms exist, the on_* name is selected. To use a different method
name, bind it explicitly, for example @mount: initialize in @settings,
with a public initialize(&mut self, ctx: &NodeContext) method. An explicit
binding replaces automatic selection for that lifecycle event.
on_tick is driven by the runtime, with no fixed frame-rate guarantee. Use
elapsed time for behavior that should progress at a stable real-time rate;
ctx.elapsed_frames and ctx.elapsed_millis expose the runtime's clocks.
For ordinary visual transitions, start with the facilities in
Motion.
Subscriptions registered through NodeContext::subscribe are automatically
cleared when their node unmounts. Other resources owned by your application
still need the cleanup appropriate to that integration. See
Subscriptions and effects
for callback scheduling and lifetime details.
Dispatch custom events
A reusable control can report an application-level action such as “advance,”
“save,” or “dismiss.” Its caller chooses how to respond. Use
NodeContext::dispatch_event to connect that named action to a handler on the
caller.
Consider an Advance control whose internal Button should ask Notes to update
progress. Add this declaration to lib.rs, keeping the existing
use pax_kit::*; import:
#![allow(unused)] fn main() { #[pax] #[inlined( <Button width=100% height=100% label="Advance" @button_click=self.activate /> )] pub struct AdvanceButton {} impl AdvanceButton { pub fn activate(&mut self, ctx: &NodeContext, _event: Event<ButtonClick>) { ctx.dispatch_event("advance") .expect("AdvanceButton requires an @advance binding"); } } }
#[inlined(...)] associates this short template with AdvanceButton. The
native Button calls activate on that component. Its handler then dispatches
the custom name "advance". The name is a string literal; the method accepts
an &'static str. Choose a name for the action your component offers, keeping
it distinct from built-in input event names.
Replace the panel's original Button in lib.pax with:
<AdvanceButton x=24px y=76px width=160px height=28px
@advance=self.advance_from_control
/>
Add the receiver to Notes:
#![allow(unused)] fn main() { impl Notes { pub fn advance_from_control(&mut self, _ctx: &NodeContext) { self.progress.set((self.progress.get() + 0.25).min(1.0)); } } }
The @advance binding on the component invocation registers that receiver.
Pressing the Button calls AdvanceButton::activate, which queues advance;
Pax then calls Notes::advance_from_control. The two handlers belong to
different component instances. The control needs no access to Notes' fields,
and the caller can reuse the control with another response elsewhere.
Context and receiver
The context passed to dispatch_event identifies the emitting component.
Here ctx is the internal Button's context, whose containing component is
AdvanceButton. Pax looks for advance on that component's invocation. In
the receiver, self is Notes while the context describes the AdvanceButton
invocation, following the inline-binding scope described above.
This mechanism connects a component to its caller. It does not search the tree for a matching name or broadcast the action to every ancestor. Use the context supplied to the emitting handler and connect the action explicitly at the call site. A root component has no outer caller to receive such an action.
Bind custom event names inline, as @advance=self.advance_from_control.
Custom names are not currently supported in an @settings handler
declaration. That restriction is separate from the built-in bindings, such
as @button_click, that settings blocks do support.
Delivery and data
dispatch_event returns Result<(), String>. It checks that the named
receiver is registered before adding the event to the runtime's queue.
Ok(()) means the event was queued; the receiving handler has not run when
the call returns. Delivery happens in the custom-event phase at the end of
a runtime tick. Avoid reading state immediately after dispatch under the
assumption that the receiver has already updated it.
The example uses expect because the control requires an @advance binding.
If the action is optional, handle the returned error according to that
component's contract. A missing receiver does not create a new event binding.
Custom callbacks have two arguments, &mut self and &NodeContext, with no
Event<T> payload. This API sends a name only: it has no argument for custom
data, no return value from the receiver, and no input event to cancel. Use
properties or shared state for data the receiver needs, and remember that a
queued receiver observes that state at delivery time. If several queued
actions each need distinct data, the application must preserve those values
explicitly rather than overwrite a single shared field.
The original Button event still follows its ordinary delivery rules.
Dispatching advance neither cancels that input nor stops its propagation.
If the same application action is also bound to a bubbling @button_click,
both paths can run it. Use the custom action as the control's outward
interface and keep the native activation handler inside the control.
For split-file components, value inputs, bind:, and scoped stores, continue
with Components and Composition.
Event delivery
The remaining sections are useful when interactions span several elements, share shortcuts, or depend on pointer geometry.
Targeting and propagation
Pointer input begins with hit testing: Pax finds the topmost eligible node
under the pointer. Remember that earlier siblings in a Pax template are
drawn above later siblings; see
Templates. Native controls
can also send events directly to their corresponding node, as a Button does
for @button_click.
Common pointer and control events then travel from the receiving node through its template parents. This is event bubbling. Each visited node runs its applicable handlers with its own context, which is why an inline handler and a component-level handler can both observe one action.
The route follows template ancestry. Projection and component composition can make that ancestry different from the visual container arrangement. Do not infer the entire event path from which rectangles overlap on screen.
Not every event family bubbles. For example, @mouse_over and @mouse_out
dispatch locally, while keyboard events use the global delivery described
below. The click/tap choice described earlier is made at each node visited
during activation propagation.
Default actions
event.prevent_default() requests that the chassis suppress the platform's
default response. The arrow-key handler uses it to request that the browser
not perform its normal arrow-key action for that handled press.
Default prevention leaves Pax's handler delivery intact: other handlers and
template parents still receive the event. The current Event API has no
stop_propagation method.
Whether the request can suppress an action depends on the chassis and event. On web, it also depends on the browser listener's cancellation rules; the current touch-start, touch-end, and touch-cancel listeners are passive. Do not rely on default prevention as a portable way to take ownership of a gesture or prevent a native control from changing its value.
Keyboard delivery
Keyboard events currently dispatch across the mounted Pax tree, so multiple components with keyboard handlers may receive the same key. If a shortcut belongs to one active view or mode, check that application state in the handler before acting on it.
On web, the chassis forwards these global keyboard events only while the
document body is the active element. When a Textbox or another native DOM
element has focus, its keyboard input stays with that control. A component's
@key_down binding does not by itself give that component keyboard focus.
Read Native Controls for the control-specific
focus and accessibility model.
Events and coordinates
Mouse and touch positions use Pax's window coordinate space. An interaction
inside a translated, scaled, rotated, or scrolling node needs local
coordinates. Use ctx.local_point to convert through the node handling the
event.
For example, add a pad below the title editor:
<Rectangle x=24px y=320px width=320px height=100px
fill=rgb(220, 216, 206) corner_radius=12
@mouse_move=self.track_mouse
/>
Import Point2 alongside the existing pax_kit import and add this handler:
#![allow(unused)] fn main() { use pax_kit::math::Point2; impl Notes { pub fn track_mouse(&mut self, ctx: &NodeContext, event: Event<MouseMove>) { let local = ctx.local_point(Point2::new(event.mouse.x, event.mouse.y)); self.progress.set(local.x.clamp(0.0, 1.0)); } } }
Moving across the pad maps its left edge to zero progress and its right edge
to one. local_point returns normalized coordinates: (0, 0) is the node's
local origin and (1, 1) is its opposite corner. To work in local pixel-like
units, read (width, height) from ctx.bounds_self.get() and multiply
local.x * width and local.y * height. These are layout units, not a promise
of physical display pixels.
The conversion includes the node's transform and ancestor Scroller
presentation offsets. Convert inside the handler using its current context;
subtracting only the element's x and y misses rotation, scale, and moving
scroll content. Read more about Layout and
Scrolling.
Touch sequences and capture
For a custom touch interaction, use @touch_start, @touch_move,
@touch_end, and @touch_cancel. Each payload contains touches with a
position, movement deltas, and an identifier. Retain the identifier your
interaction accepts so subsequent events can be matched to it.
The primary touch captures the topmost hit node at TouchStart. While that
capture remains valid, subsequent move, end, and cancel events target it even
when the finger leaves its bounds; the usual template-parent delivery still
applies. This is useful for keeping a drag attached to its original control.
It does not establish equivalent mouse capture or a general multi-touch
gesture recognizer.
Clear pressed or dragging state in both end and cancel handlers. TouchEnd
finishes a normally released sequence. TouchCancel handles an interrupted
sequence, such as one aborted by the platform. A cancelled sequence does not
synthesize a click or tap. Use the activation event for the final action and
the lower-level events for transient feedback when those responsibilities
need to stay separate.
Touches inside a Scroller
On iOS and iPadOS, the native Scroller's touch observation lets custom child
content receive the start immediately and continue receiving moves while
scrolling. Normal release produces TouchEnd; recognition of a scroll
disqualifies tap activation without requiring cancellation of that lower-level
stream. This allows a child to light up on contact, follow the touch, and
clear its feedback when the finger lifts.
Because local conversion includes the moving content transform, a contact that moves with the scroll stays approximately fixed on its original child in that direction. Native controls and platform gestures can impose further input behavior, so test the particular control and target together. On web, tap qualification also uses a movement tolerance; a moved touch sequence need not produce activation on release.
Read further
Continue with Components and Composition for reusable component interfaces, shared state, and custom event contracts.
Components and Composition
A panel becomes more useful when it can appear in several places, with a different title or progress value each time. A component gives that panel a name and an interface: the values it accepts, the actions it offers, and the content its caller can supply.
This chapter extracts the Field notes panel from Properties and Events and Rust into a small reusable card. It then builds on the same boundary to explain conditional views, lists, slots, and shared state. If you are new to Pax source, start with Templates.
Make a reusable component
Create src/note_card.rs and src/note_card.pax beside your application's
lib.rs and lib.pax:
src/
├── lib.rs Notes: application state and actions
├── lib.pax places cards and supplies their inputs
├── note_card.rs NoteCard: its public properties and defaults
└── note_card.pax the card's internal interface
In src/note_card.rs, declare the card's inputs:
#![allow(unused)] fn main() { use pax_kit::*; #[pax] #[file("note_card.pax")] #[custom(Default)] pub struct NoteCard { pub title: Property<String>, pub progress: Property<f64>, } impl Default for NoteCard { fn default() -> Self { Self { title: Property::new("Untitled note".to_string()), progress: Property::new(0.0), } } } }
The type has #[pax] and an associated template, like the main component.
Only the application root has #[main]. Public properties form the card's
value interface; here progress uses a zero-to-one range. The custom default
provides useful values when a caller omits an input. Each new card instance
gets its own initial properties.
Give src/note_card.pax the display portion of the panel:
<Text x=24px y=24px width={100% - 48px} height=36px
text={self.title}
style={font_size: 24px, fill: rgb(32, 40, 48)}
/>
<Text x=24px y=72px width={100% - 48px} height=28px
text={"Progress: " + (self.progress * 100) + "%"}
style={font_size: 16px, fill: rgb(72, 80, 88)}
/>
<Group x=24px y=116px width={100% - 48px} height=12px>
<Rectangle width={(self.progress * 100)%} height=100%
fill=rgb(55, 120, 90) corner_radius=6
/>
<Rectangle width=100% height=100%
fill=rgb(220, 216, 206) corner_radius=6
/>
</Group>
<Rectangle width=100% height=100%
fill=rgb(246, 241, 230) corner_radius=12
/>
The card uses the layout area supplied by its caller. The percentage widths follow that area; the fixed offsets give this example its padding. Its background remains last so the text and bar render in front. For sizing and container choices, read Layout and Responsiveness.
Use the card
Here is a complete src/lib.rs for the two-card example:
#![allow(unused)] fn main() { use pax_kit::*; pub mod note_card; pub use note_card::NoteCard; #[pax] #[main] #[file("lib.pax")] #[custom(Default)] pub struct Notes { pub title: Property<String>, pub progress: Property<f64>, } impl Default for Notes { fn default() -> Self { Self { title: Property::new("Field notes".to_string()), progress: Property::new(0.25), } } } impl Notes { pub fn advance(&mut self, _ctx: &NodeContext, _event: Event<ButtonClick>) { self.progress.set((self.progress.get() + 0.25).min(1.0)); } } }
pub mod includes the Rust module, and pub use makes NoteCard available
at the crate root. This is a straightforward pattern for a project's reusable
components. The .pax file is associated through #[file(...)]; it needs no
separate Rust module declaration.
Template paths are looked up from the package root, with a src/ fallback.
If you move the pair into a deeper directory, update that path explicitly,
for example #[file("src/cards/note_card.pax")], along with the Rust module
wiring. The path is not relative to the Rust file containing the attribute.
In src/lib.pax, place two cards and an application-level button:
<Button x=24px y=24px width=160px height=28px
label="Advance" @button_click=self.advance
/>
<NoteCard x=24px y=72px width=320px height=160px
title={self.title} progress={self.progress}
/>
<NoteCard x=24px y=248px width=320px height=160px
title="Sketchbook" progress=0.75
/>
The first card starts at 25 percent and follows the application's progress. The second displays 75 percent independently. Pressing Advance changes only the first card. Remove the second card's title and progress attributes to try its defaults: “Untitled note” and zero progress.
Restart the app after adding the Rust files and declarations. The default hot-reload lane updates template edits; see Developer Workflow for the application-logic reload options.
Inputs and state ownership
In lib.pax, {self.title} reads Notes.title. Inside note_card.pax, the
same expression reads that particular NoteCard.title. The attribute on the
invocation connects the two scopes. A child template can use its own fields
without knowing the containing application's type or variable names.
Values supplied by the caller take the place of the child's defaults for those inputs. Use defaults for fallback state, and avoid unconditionally writing caller-supplied inputs in a mount handler. Such a write can replace the displayed value even though the parent is meant to own it.
An ordinary binding such as progress={self.progress} derives the child's
input from the parent. It does not provide write-through to the parent.
When the parent owns progress, have application actions update that source.
The card's label and bar then follow its input. Properties
explains what happens if Rust writes directly to a computed field.
For a component designed to edit a shared value, bind: explicitly connects
the same property handle at both ends:
<NoteCard x=24px y=72px width=320px height=160px
title={self.title} progress=bind:progress
/>
The read-only card looks the same. If it gains an editing handler that calls
self.progress.set(...), that handler can now update Notes.progress too.
Both fields must have compatible property types; bind: takes a property
identifier, not an arbitrary formula. Use it when editing that value is part
of the component's intended interface. See
PAXEL for the binding syntax and
property handles for the Rust model.
State used only inside one card can stay on that card. State shared by several cards usually belongs in their common owner. For an action such as “advance this note,” a named event can let the owner decide how state should change; we will build that connection below.
Data-driven components
A component invocation can be part of a conditional branch or a repeated group. The template describes which instances should exist for the current state; Rust changes that state in response to actions.
Conditional content
For example, replace the first card with a completion message once progress reaches one:
<Group x=24px y=72px width=320px height=160px>
if self.progress < 1.0 {
<NoteCard width=100% height=100%
title={self.title} progress={self.progress}
/>
} else {
<Text width=100% height=100% text="Ready for the next idea."
style={font_size: 20px, fill: rgb(55, 120, 90)}
/>
}
</Group>
Conditions use PAXEL directly after if; the braces enclose template
content. An else if chain can choose among several states, and else is
optional. Pax uses the first true branch, or the final else when present.
Each branch can contain several elements.
Changing branches removes the old content and mounts the new content. Local
state belongs to those component instances: if a card is removed and later
created again, its local state starts again. Keep state that must survive
that change in Notes or another longer-lived owner. Exit transitions can
retain departing content temporarily, and rapid re-entry can reuse it; see
Animation and Motion for that lifecycle.
A structural if controls which elements exist. A conditional inside
@settings changes settings on
existing elements. Choose according to whether the interface needs different
content or different styling.
Ranges and collections
Use for to repeat template content. A range is useful for a fixed set of
decorative elements or positions:
for i in 0..3 {
<Text x=24px y={(24 + i * 32)px} width=240px height=28px
text={"Position " + i}
/>
}
This produces positions 0, 1, and 2; the upper bound is excluded. A loop
receives each value as its item. With for (item, i) in ..., the second
binding is its current zero-based index. Bindings are available inside that
loop's body; nested loops can give their own items and indices distinct names.
For application data, add a small record to lib.rs:
#![allow(unused)] fn main() { #[pax] pub struct NoteEntry { pub id: usize, pub title: String, } }
Add pub entries: Property<Vec<NoteEntry>> to Notes. In its existing
Default implementation, initialize that field with:
#![allow(unused)] fn main() { entries: Property::new(vec![ NoteEntry { id: 10, title: "Field notes".to_string() }, NoteEntry { id: 20, title: "Sketchbook".to_string() }, NoteEntry { id: 30, title: "References".to_string() }, ]), }
The record's plain fields describe one value in the collection. The outer
property publishes changes to the collection; use set or update as
described in Properties.
Keyed lists
Replace the two individual cards with a list:
<Group x=24px y=72px width=320px height=528px>
for (entry, i) in self.entries key entry.id {
<NoteCard y={(i * 176)px} width=100% height=160px
title={entry.title}
/>
}
</Group>
Here i determines position, while entry.id determines identity. If the
entries change from [10, 20, 30] to [30, 10, 20], Pax reuses the existing
component groups in their new order. Local state, such as a card's expanded
view or an in-progress edit, follows the entry with the same key. New keys
mount new groups; removed keys leave the active list and can play exit
transitions. A loop body may contain several elements, which share that
iteration's identity.
Choose a string or integer key that is stable for the item's lifetime and unique within that loop. The current index is generally a poor key for a list that can be reordered. Duplicate or unsupported key values cause a runtime warning and positional fallback, so check the data instead of relying on that fallback to preserve state.
An unkeyed loop reuses positions. It is useful for fixed grids and repeated decoration; for mutable application lists, use an item identifier. Keys govern reuse within their own loop, not identity across unrelated loops or branches. Layout remains a separate choice: this example uses explicit row positions; Stacker and Scroller can arrange or reveal a larger collection.
Explore Transition Grid to see a keyed list being inserted into, removed from, and reversed. Its child components animate while Stacker owns their placement. The tile counters are stored in the parent collection; the child's animated color is local component state.
Slots
Sometimes the reusable part is a frame around content its caller supplies. A card frame might reserve a header area while accepting text, controls, or another component in its body. Slots specify where that caller-provided content appears inside the frame's template.
Add a component declaration to lib.rs:
#![allow(unused)] fn main() { #[pax] #[file("note_frame.pax")] pub struct NoteFrame {} }
In src/note_frame.pax, use one explicit slot and a remainder slot:
<Group x=24px y=24px width={100% - 48px} height=32px>
slot(0)
</Group>
<Group x=24px y=72px width={100% - 48px} height={100% - 96px}>
slot()
</Group>
<Rectangle width=100% height=100%
fill=rgb(246, 241, 230) corner_radius=12
/>
Use it from lib.pax with content between its opening and closing tags:
<NoteFrame x=24px y=72px width=320px height=220px>
<Text width=100% height=100% text={self.title}
style={font_size: 24px, fill: rgb(32, 40, 48)}
/>
<Text width=100% height=32px text="Make room for the next idea."
style={font_size: 16px, fill: rgb(72, 80, 88)}
/>
<Button y=48px width=100% height=36px label="Advance"
@button_click=self.advance
/>
</NoteFrame>
slot(0) projects the first child into the header area. slot() projects all
children not consumed by an earlier active slot site in this component; here
the body Group receives the second Text and the Button. The caller positions
those body elements within the area the frame supplies. The slot syntax is
positional, with zero-based indices. These children remain authored in
Notes: their self.title and self.advance still refer to Notes, even
though the frame supplies their layout containers.
A component can forward its slots through another component. For example,
placing slot(0) inside a Scroller keeps that slot associated with the
component that authored it. The Scroller's own slots handle its received
content separately; they do not consume the outer component's children.
Slots project the existing child content. They do not make a new independent
copy at each insertion site. An explicit index that is out of range or already
consumed renders empty and produces a warning. An empty remainder slot is
valid and silent. Put fixed slot sites before the remainder site; an earlier
slot() can consume everything before a later explicit site gets a chance.
Consumption follows the active slot sites in template-tree order. Conditional
sites or a changing slot index can change which children reach the remainder.
Likewise, if and for in caller content can change the currently available
projected children. Wrap several elements in one Group when they should be
supplied together as one child. The
slot-projection-resolver example
explores these dynamic cases.
The frame's private background and layout structure stay inside its template.
Caller content and private implementation structure serve different roles;
container code should use NodeContext::received_children for its semantic
content rather than treating all runtime descendants as supplied children.
Custom container internals are beyond this chapter.
Component actions
A reusable component can name an action without prescribing the application's response. For example, an Advance control can ask its owner to advance a note. The owner may update progress, validate some data, or choose another note.
The complete custom-event example in Events
builds an AdvanceButton that dispatches advance to a handler on Notes.
Use that pattern when the component should express an intent and leave the
state change to its caller. Use an input for a value the component reads,
and an explicit bind: when editing the shared value is part of its interface.
Custom events carry a name only, so design their data interface alongside the component's properties or shared state. Keep the action's required or optional binding explicit. Events owns the sender/receiver code, context, delivery timing, and error behavior; this chapter's component boundary is what makes that connection reusable.
Shared state farther down the tree
Explicit inputs and actions keep a small component easy to reuse. For state needed throughout a larger subtree, a local store lets descendants find a shared value without passing it through every intermediate component.
Define a type that names the store's purpose in lib.rs:
#![allow(unused)] fn main() { pub struct NotesStore { pub progress: Property<f64>, } impl Store for NotesStore {} }
Provide it from the owning component's mount method:
#![allow(unused)] fn main() { impl Notes { pub fn on_mount(&mut self, ctx: &NodeContext) { ctx.push_local_store(NotesStore { progress: self.progress.clone(), }); } } }
If Notes already has on_mount, add this registration to that method.
Cloning the property shares the owner's handle. The store does not need
#[pax] because it is accessed from Rust rather than used as a template
component or value.
A descendant handler with a ctx: &NodeContext argument can obtain the
handle and reset progress:
#![allow(unused)] fn main() { let progress = ctx .peek_local_store(|store: &mut NotesStore| store.progress.clone()) .expect("this component must be inside a NotesStore provider"); progress.set(0.0); }
Import NotesStore from its defining module in the consumer. The lookup walks
the runtime property stack and finds the nearest store of that Rust type;
it returns Err when none is available. This example requires a provider.
Handle the error explicitly if the consumer should also work on its own.
Use a distinct store type for each role. Inserting the same type again in one stack frame replaces that frame's store; a nearer provider shadows an outer one. Lookup follows runtime scope, including component and projection scope, so visual proximity alone does not establish access. Keep the borrowed-store closure short: cloning a needed property handle lets subsequent work happen after the borrow ends, as above.
The store supplies access to state; the Property inside it supplies reactive
updates. A plain Rust field in a store does not become reactive just by being
stored there. The
router-playground example
uses a scoped store to share its mobile-menu state with navigation components.
Read Properties for handle lifetime
and thread constraints.
For an advanced alternative that implements an element directly through the Rust runtime, read Primitives. That chapter covers when to use a primitive, how to author one, and its rendering and lifecycle responsibilities.
Read more
You can now give a reusable view its own interface, choose the owner of its state, and assemble instances from data and caller-provided content. Continue with Layout and Responsiveness to make those components fit different spaces. Scrolling covers larger collections, Routing organizes screens, and Motion adds transitions as content enters, leaves, or changes position.
Primitives
A component gives a reusable interface its own template, properties, and actions. A primitive is a more advanced alternative: it implements a template element directly in Rust through Pax's runtime interfaces. This gives its author control over lower-level behavior such as drawing, child expansion, and integration with a platform surface.
You already use primitives when you place elements such as Ellipse,
Group, and Text in a template. Their standard-library implementations
connect those tags to the renderer and runtime. You can use the same extension
point to build a new kind of element in your own project.
Components and primitives
From the caller's perspective, both can appear as template tags and expose
Property<T> inputs. Their implementations differ:
| Kind | Implementation |
|---|---|
| Template-backed component | A #[pax] type with #[file(...)] or #[inlined(...)]; its template composes other nodes. |
| Primitive | A #[pax] type with #[primitive("...")]; a Rust InstanceNode implements its runtime behavior, with no attached .pax template. |
| Data type | A #[pax] type without a template or primitive declaration; it describes values such as the NoteEntry record. |
For application UI, begin with components and composition.
A reusable illustration can combine Path and the existing drawing elements;
a new route presentation can use a custom route branch.
A primitive becomes useful when the capability needs runtime hooks those
elements do not expose: a specialized drawing operation, a new container
behavior, or a platform integration.
That additional control comes with responsibility for rendering, lifecycle, and target-specific behavior. Choose it for the capability you need; changing a component into a primitive does not automatically improve performance.
This chapter assumes familiarity with Rust, properties, and component composition. It starts with a minimal registration scaffold, then explains the obligations of a working primitive and points to implementations in the engine and standard library.
Authoring primitives
Declare and connect a runtime node
The extension point is available in userland. The compiler generates a
factory that calls your implementation through the import path in
#[primitive(...)]; adding a node does not require an enum entry in
pax-std.
There are two Rust types to understand:
- The
#[pax]type declares the properties visible to authors. - Its
InstanceNodeimplementation supplies construction and runtime hooks. A sharedBaseInstancecarries the compiler-provided configuration.
The following scaffold establishes that connection. It deliberately has no
drawing or platform side effects yet. Create src/runtime_marker.rs:
#![allow(unused)] fn main() { use pax_kit::*; use pax_kit::pax_engine::api::Layer; use pax_kit::pax_engine::pax_runtime::{ BaseInstance, ExpandedNode, InstanceFlags, InstanceNode, InstantiationArgs, }; use std::rc::Rc; #[pax] #[primitive("crate::runtime_marker::RuntimeMarkerInstance")] pub struct RuntimeMarker { pub value: Property<f64>, } pub struct RuntimeMarkerInstance { base: BaseInstance, } impl InstanceNode for RuntimeMarkerInstance { fn instantiate(args: InstantiationArgs) -> Rc<Self> { Rc::new(Self { base: BaseInstance::new(args, InstanceFlags { invisible_to_slot: false, invisible_to_raycasting: true, layer: Layer::DontCare, is_component: false, is_slot: false, }), }) } fn base(&self) -> &BaseInstance { &self.base } fn resolve_debug( &self, f: &mut std::fmt::Formatter, _expanded_node: Option<&ExpandedNode>, ) -> std::fmt::Result { f.debug_struct("RuntimeMarker").finish() } } }
The three methods shown are required by InstanceNode. The flags describe a
non-drawing node that does not intercept pointer hit testing. A drawing
primitive would choose Layer::Canvas and provide rendering behavior; a
native surface has a different layer and platform-message lifecycle.
In lib.rs, add the module and expose its author-facing type:
#![allow(unused)] fn main() { pub mod runtime_marker; pub use runtime_marker::RuntimeMarker; }
It can now be instantiated in a template:
<RuntimeMarker value=0.5 />
It occupies a runtime node but paints nothing. Use this as a registration
scaffold, then implement the specific hooks your capability needs. The
crate::... path above refers to this application's Rust module. A primitive
distributed in a library needs a path accessible from the consuming
application's generated code; the standard library uses paths such as
pax_std::drawing::ellipse::EllipseInstance.
Implement behavior at the right level
An InstanceNode can serve several expanded occurrences, for example when
a template node is repeated. Each occurrence has an ExpandedNode with its
own properties, geometry, children, and lifecycle state. Keep per-occurrence
state there, or in state captured by an effect attached to that occurrence.
Putting a mutable per-item value directly on the shared instance can make
repeated items interfere with one another.
Runtime hooks receive the expanded node. Access its typed properties with
expanded_node.with_properties_unwrapped(|properties: &mut RuntimeMarker| { ... }), and use transform_and_bounds for its resolved geometry. This is
lower-level access than a component handler's NodeContext; keep ordinary
application actions in event handlers.
The main implementation responsibilities depend on the capability:
- Reactive work: bind an effect in
handle_mountto the properties and geometry it reads. Standard drawing primitives usechanged_listenerto mark their retained drawing dirty. Reserveupdatefor work that cannot be driven by those dependencies; it requires opting in throughrequires_non_reactive_update. - Canvas drawing: implement
renderthroughRenderContext. Follow the retained-node begin/end protocol, use the correct surface-local transform, apply inherited opacity, and invalidate drawing when its inputs change. The currentEllipseInstancedemonstratesbegin_bounded_canvas_nodeand clearing the dirty flag only afterend_nodesucceeds. Unbounded geometry needs suitable coverage bounds. - Coverage and interaction: keep coverage paths, coverage opacity, and
hit testing consistent with what the node draws. These hooks affect masks,
native/canvas ordering, and pointer selection. The default hit test uses
layout bounds; unusual shapes may need
ray_cast_test. - Children and cleanup: a container must manage child expansion and scope; overriding mount behavior also means preserving the needed child setup. Release subscriptions, resources, and native surfaces on unmount. Respect the received-versus-projected child model for container content.
These runtime interfaces evolve with the engine. Work from the implementation in the Pax version your project uses and test the capability on each backend you intend to support. Exercise repeated instances, reactive updates, resize, transforms, opacity, clipping, and removal—not just the first rendered frame. Check both debug and release builds.
Native integration and engine changes
A userland primitive can use the existing rendering and runtime interfaces.
A completely new native widget may also need message types, platform-side
creation/update/deletion handlers, event serialization, and accessibility
behavior. Setting Layer::Native alone does not create that support.
That work can span pax-message, the web interface, and the Swift interfaces
for macOS, iOS, and iPadOS. New renderer operations or template/control-flow
syntax similarly need engine or compiler support. Keep the supported-target
boundary explicit.
When adding data that must cross the compiler/runtime boundary, update the release representation and its round-trip tests too. A primitive's normal property declaration participates in generated descriptors; adding a new manifest field or value form entails a broader audit. See How Pax Runs for the execution model and the maintainer appendix for cartridge details.
Source examples to study
Read these alongside the code in your checkout:
Ellipse/EllipseInstance— a bounded canvas primitive: properties, dirty tracking, geometry, material-aware drawing, and coverage.Group/GroupInstance— child expansion, layout measurement, and conditional native-surface integration.Text/TextInstance— native create/update/delete messages and text measurement. Follow the message to the target interface for the platform half.InstanceNode,BaseInstance, andInstantiationArgs— the runtime contract and default hook implementations.ComponentInstance— the engine implementation behind ordinary template-backed components, including scope and slot projection.
The runtime API reference is useful for orientation. The source links follow the repository's development branch; use the corresponding files at your dependency's release when implementing a primitive.
Read more
Components and Composition covers reusable template-backed UI, slots, and shared state. Drawing and Styling introduces the existing drawing elements, and How Pax Runs explains the runtime and rendering model. For the lower-level interface, continue with the runtime API reference and the source examples above.
Routing
A useful URL names a place in your application: a project, a member's profile, or the settings panel someone wants to share. Pax connects that location to a component tree. On the web, the location follows the browser URL and its history. On macOS, iOS, and iPadOS, the same route tree can select screens using an in-app location.
This chapter builds on Templates, Events and Rust, and Components and Composition. Start with ordinary routes, then add nested screens or route transitions as the application grows.
Example
The Router Playground combines a persistent navigation area, nested guide
and team routes, and an inspector for the active route data. Open
src/route_outlet.pax to find the outer router; src/team_panel.pax shows
the nested router and its navigation links.
The example starts at Landing. Open Menu in the narrow layout to find the guide, team screens, and fallback routes.
Choose Open standalone to explore the playground with its own browser address bar:
- Open a team member and compare the outer and inner scope shown by the inspectors.
- Follow a team settings link. The outer team remains selected while the inner route and remainder change.
- Visit an unknown path and inspect the fallback's unconsumed segments.
- Use Back and Forward, then reload the selected screen.
The docs host keeps the example's route in its pax_route URL query parameter,
so navigation and reload stay inside the example's directory. Its navigation
does not change the address of this documentation page. A regular standalone
Pax application uses browser paths, as described below. The playground demonstrates runtime
routing. Its responsive route trees are not a template for indexable static
metadata; see Web route metadata for that purpose.
Routes and history
Three pieces work together:
Routerreads the current location and chooses a branch.Routedeclares a path and the content that belongs there.Linkor Rust'sNodeContext::navigate_torequests a new location.
Navigation updates the location; the router reacts by selecting the matching subtree. Your event handler does not need to hide the old page and show the new one manually.
On the web, same-origin navigation in the current tab updates browser history without restarting Pax. Back and Forward feed location changes into the same router. A fresh visit or browser reload starts a new application at the requested URL, so direct-link hosting is part of making a routed app usable.
Basic shape
Router has no visual surface of its own. Place it inside the layout area
that should contain the selected screen.
Here is a small two-page application. In src/lib.rs:
#![allow(unused)] fn main() { use pax_kit::*; #[pax] #[main] #[file("lib.pax")] pub struct App {} }
In src/lib.pax:
<Group x=24px y=24px width={100% - 48px} height={100% - 48px}>
<Link width=100px height=36px url="/">
<Text width=100% height=100% text="Home" />
</Link>
<Link x=120px width=100px height=36px url="/notes">
<Text width=100% height=100% text="Notes" />
</Link>
<Group y=64px width=100% height={100% - 64px}>
<Router>
<Route path="/">
<Text width=100% height=48px text="A place for your ideas." />
</Route>
<Route path="/notes">
<Text width=100% height=48px text="Your field notes." />
</Route>
<Route default=true>
<Text width=100% height=48px text="This page could not be found." />
</Route>
</Router>
</Group>
</Group>
Run it with pax-cli run --target web. Follow the links, then use the browser's
Back button. As the pages become more substantial, replace the text in each
branch with a component such as <Home /> or <Notes />. The navigation can
stay outside the router and remain mounted as the page changes.
Matching paths
Route patterns are literal strings. The supported forms are:
| Pattern | Matches |
|---|---|
/ | The root of the current router's scope. |
/notes | That exact path. |
/teams/:team_id | One segment captured as team_id. |
/teams/:team_id/* | The team path and any remaining segments. |
default=true | A fallback when no explicit path matches. |
A * must occupy the final segment. It can match an empty tail, so
/teams/:team_id/* also matches /teams/design. Without the *, a pattern
must consume the complete scoped path.
The first matching explicit branch wins. Put a specific route such as
/teams/new before /teams/:team_id, and put a broad catch-all after more
specific routes. There is no automatic specificity ranking. A default branch
is considered only after all explicit branches fail, regardless of where the
default appears in the file. Prefer one default per router. With no match and
no default, that router contributes no active content.
Leading and trailing slashes do not change how a route pattern matches its
scope. In a nested router, members/:member_id and /members/:member_id
both describe a local path; the leading slash does not escape to the root.
Writing routes
Links in templates
Wrap the visible content of a navigation control in a Link:
<Link width=200px height=44px url="/teams/design">
<Text width=100% height=100% text="Open the design team" />
</Link>
The default target is Target::Current. Use target=Target::New when an
external web destination should open in a new browsing context:
<Link width=200px height=44px url="https://pax.dev" target=Target::New>
<Text width=100% height=100% text="Visit Pax" />
</Link>
Give the wrapper and its content a useful interaction area. Link participates in Pax's event system; see Accessibility and Native Controls for the current accessibility boundaries.
Navigation from Rust
Use navigate_to after an action, or when Rust determines the destination.
For example, bind a button to this handler on your component:
#![allow(unused)] fn main() { impl App { pub fn open_team(&mut self, ctx: &NodeContext, _event: Event<ButtonClick>) { ctx.navigate_to( "/teams/design?view=board#activity", NavigationTarget::Current, ); } } }
<Button width=180px height=36px label="Open team"
@button_click=self.open_team />
This requests navigation through the active platform interface. Use
root-relative paths such as /teams/design for destinations within the app.
On the web, a relative destination is resolved against the current browser
URL, not the nearest router's scope. Navigating to settings from
/teams/design can therefore differ from navigating there from
/teams/design/.
For a web Current destination, matching scheme, host, and port keep the
navigation in the Pax session. Other origins and New destinations use
ordinary browser navigation. The URL should describe the intended location;
route matching does not validate that a user may access its data. Perform
authorization in the systems that own that data.
Native locations
On macOS, iOS, and iPadOS, current-target relative paths update Pax's virtual in-app location. Query parameters and fragments travel with it. External destinations are handed to the operating system to open.
A native router does not provide browser chrome, a native back-stack control, or automatic universal-link registration. Supply the application's navigation controls and configure any OS-level deep-link integration separately. Prefer explicit destinations for a portable “Back to team” control.
The route binding
Inside the selected branch, Pax supplies a reactive route value. Its fields
describe this match:
| Field | Meaning |
|---|---|
route.location | The structured location seen by this router. |
route.global_location | The full application location. |
route.params | Named captures from this branch's :param segments. |
route.remainder | A list of path segments left for a nested router. |
route.consumed_segments | The number of segments consumed before that remainder. |
route.is_exact | Whether the remainder is empty. |
For example, inside /teams/:team_id/*:
<Text width=100% height=36px text={"Team: " + route.params.team_id} />
Both location objects contain path_segments, query, and fragment.
For the web URL
/teams/design/members/ada?tag=ui&tag=motion#activity:
- The global path segments are
["teams", "design", "members", "ada"]. querymapstagto["ui", "motion"]; repeated keys retain their values.fragmentis an optional string containing"activity", without#.
The web interface decodes URL components before delivering these values. Query and fragment data do not decide which path pattern matches. They can drive the selected screen's filters or other UI. Check optional values and missing keys before consuming them, and encode user-supplied values when constructing URLs.
A fragment is location data. Scrolling a Pax Scroller to a corresponding
piece of content is application behavior; it is not implied by adding
#activity to a route. See Scrolling and Viewports.
Nested routers
A nested router receives the nearest ancestor route's remainder. This lets a component own the routes beneath its part of the application.
For example, let the outer route supply the team identifier to TeamPanel:
<Router>
<Route path="/teams/:team_id/*">
<TeamPanel team_id={route.params.team_id} />
</Route>
<Route default=true>
<NotFound />
</Route>
</Router>
Declare pub team_id: Property<String> on TeamPanel. Its template can use
that property for persistent team chrome, while its own router chooses the
team's inner screen:
<Group width=100% height=100%>
<Text width=100% height=40px text={"Team: " + self.team_id} />
<Group y=56px width=100% height={100% - 56px}>
<Router>
<Route path="/">
<TeamOverview />
</Route>
<Route path="members/:member_id">
<MemberDetail member_id={route.params.member_id} />
</Route>
<Route path="settings/*">
<TeamSettings />
</Route>
<Route default=true>
<TeamNotFound />
</Route>
</Router>
</Group>
</Group>
The screen components here stand for your application's content. For
/teams/design/members/ada, scope changes in two steps:
| Router | Input segments | Captures | Remainder |
|---|---|---|---|
| Outer | teams / design / members / ada | team_id = design | members / ada |
| Team panel | members / ada | member_id = ada | Empty |
The inner route describes the inner match. Its params do not automatically
merge the outer route's captures. Passing team_id into the component gives
it a stable, explicit name alongside the inner member_id.
route.global_location continues to describe the complete location at both
levels. Query and fragment data are preserved when the path is scoped.
Use path="/" for a team's overview and default=true for its unknown
subpaths. Using a default alone for the overview would also show it for
unrecognized tails. The ancestor's terminal * is what makes additional
path segments available to the inner router.
Route state and lifetime
Pax uses the route declaration and its captured parameters to identify an active route instance.
- Changing only query data, a fragment, or a catch-all remainder updates the existing instance's reactive route context.
- Changing a captured parameter creates a new instance. Moving from
/teams/designto/teams/engineeringcreates a new team subtree. - Changing to a different ordinary route removes the old branch, subject to its exit lifecycle. Returning later generally creates it again.
This is why a team shell can keep local state while its inner router switches from members to settings. State that must survive leaving the team should have an owner outside that route: a longer-lived component, a local store provided above the router, or your application's persistence layer. Components and Composition explains the store pattern.
Browser history records locations, not snapshots of every component's properties. Do not rely on Back or a page reload to restore unsaved form state.
Route branch kinds
Use an ordinary Route for mutually exclusive screens. RouteCard and
RouteModal add a retained background for route-driven overlays.
A card supplies its own sliding shell:
<Router>
<Route path="/">
<Home />
</Route>
<RouteCard path="/details" edge=RouteCardEdge::Trailing duration=300ms>
<Details />
</RouteCard>
</Router>
Navigate from Home to /details to keep Home mounted underneath the incoming
card. The card slides from the chosen edge while a scrim fades over the
background. Available edges are Leading, Trailing, Top, and Bottom.
duration controls the transition; scrim_opacity controls the dimming.
RouteModal retains the previous route and supplies the fading scrim, while
its contents own their layout and any @in / @out movement. Set its
duration to coordinate the scrim with those content transitions.
Animation and Motion covers lifecycle timelines.
Provide a close control that navigates to the intended background route. Returning to that retained route can reveal its existing state without remounting it. The scrim does not supply an automatic close action.
An overlay entered by a fresh URL has no previous route to retain. Design that direct-load state deliberately: include the context the overlay needs, or place essential shared chrome outside the router. A browser history entry alone cannot reconstruct the previous mounted screen.
Custom route branches
You can define route branch types in your application or a reusable Rust
crate. The #[route_branch(...)] attribute tells the compiler which
properties describe the path and fallback, and whether the selected branch
retains the previous one underneath. No pax-std enum change is required.
A custom branch is useful when several routes share presentation: a padded panel, a branded sheet, or a particular enter/exit treatment. It remains an ordinary template-backed component, with properties, slots, and lifecycle timelines.
For example, create src/panel_route.rs:
#![allow(unused)] fn main() { use pax_kit::*; #[pax] #[file("panel_route.pax")] #[route_branch(path = "pattern", default = "fallback")] pub struct PanelRoute { pub pattern: Property<String>, pub fallback: Property<bool>, } }
Here pattern and fallback are names chosen by the component author. The
attribute maps them to the router's existing contract. They could also be
named path and default, as in the standard branches.
Give it a src/panel_route.pax template that presents its caller's content:
<Group x=20px y=20px width={100% - 40px} height={100% - 40px}>
slot()
</Group>
<Rectangle width=100% height=100%
fill=rgb(246, 241, 230) corner_radius=16 />
Expose the type from your application's lib.rs:
#![allow(unused)] fn main() { pub mod panel_route; pub use panel_route::PanelRoute; }
Then use it directly under a router, in the layout area for the screen:
<Router>
<PanelRoute pattern="/notes/:slug">
<Text width=100% height=40px text={"Note: " + route.params.slug} />
</PanelRoute>
<PanelRoute fallback=true>
<Text width=100% height=40px text="No matching note." />
</PanelRoute>
</Router>
Pax selects the branch and preserves its component shell. The slotted content receives the route binding, while the shell supplies padding and a background. Its other properties and timelines work as they do on ordinary components. See Slots for caller scope and projection.
The default policy replaces the previous active route. To make a custom
branch retain the previous route, add modal = true to its attribute:
#![allow(unused)] fn main() { #[route_branch(path = "pattern", default = "fallback", modal = true)] }
This is a policy for that branch type. It does not automatically add a scrim, a close button, or an animation; the component supplies those parts. Use the standard RouteModal and RouteCard implementations as examples of retained-route presentation.
Custom branches use the same literal patterns, declaration-order matching, nested scope, and parameter-based identity as the standard branches. New matching syntax or a different retention policy would need changes to the router implementation. For deeper runtime behavior, Pax also exposes primitive authoring, a separate extension point that custom route presentation usually does not need.
Web route metadata
A public web route also needs a title and description before someone
interacts with the app. Add literal RouteMetadata to the route that owns
that page:
<Router>
<Route path="/" metadata=RouteMetadata {
title: "Field notes",
description: "A small home for observations and ideas.",
index: true,
}>
<Home />
</Route>
<Route path="/notes/*" metadata=RouteMetadata {
title: "Notes — Field notes",
description: "Observations from the field.",
index: true,
social_image: "assets/notes-card.png",
social_image_alt: "An open field notebook",
}>
<Notes />
</Route>
<Route default=true metadata=RouteMetadata {
title: "Page not found — Field notes",
description: "This page could not be found.",
index: false,
}>
<NotFound />
</Route>
</Router>
Supply your own social image at the referenced asset path. title and
description are required, nonempty string literals; index is a required
boolean literal. The optional social_image and social_image_alt fields
must appear together. These are build-time declarations, so property
bindings and computed titles are not supported in this metadata block.
A nested route inherits the nearest metadata block until it declares its own.
A replacement block must still provide all required fields. Keep indexable
route topology static: placing it behind an if, for, or projected slot
prevents deterministic metadata generation and causes the web build to fail.
This includes passing an indexable router as projected children to a
template-backed layout component. Keep the router outside that projection;
responsive layout can remain inside a statically declared route.
Site-wide values
Set the public site identity in the project's Cargo.toml:
[package.metadata.pax.web]
title = "Field notes"
site_name = "Field notes"
site_url = "https://notes.example.com"
social_image = "assets/site-card.png"
social_image_alt = "Field notes"
The route's social-image pair overrides the site-wide pair. site_url must
be an absolute HTTP(S) URL without a query or fragment; it supplies canonical
URLs and absolute social-image URLs. A release web build containing an
indexable concrete route requires it. Use the actual production URL for a
published build, and keep the referenced images in the published output.
Generated entries and indexing
The web build emits route-metadata.json and route-specific entry HTML for
concrete paths that have effective metadata. For the preceding example:
.pax/build/release/web/
index.html
notes/
index.html
route-metadata.json
…the rest of the application bundle
The entry documents contain the title, description, robots directive, canonical URL when configured, and Open Graph/Twitter metadata. After in-app navigation, the web interface updates the document head from the catalog without restarting the Pax session.
Concrete and symbolic paths have different limits:
- A literal
/notescan receive its own entry document. - A terminal catch-all
/notes/*can emit the concrete prefix/notes. Arbitrary deeper URLs do not become separate pages. - A parameterized
/notes/:slugremains symbolic. The compiler does not enumerate records or generate an entry document for every possible slug. - At runtime, symbolic matches and catch-all tails receive
noindexand no canonical link. An indexable directive applies only at the route's concrete path. Declare concrete routes for pages that need individually generated metadata.
The HTML contains an application entry and its metadata; the screen itself still renders when Pax starts. An unknown direct URL initially receives the fallback document's head, then the running app applies the default route's metadata. Account for this when checking previews from crawlers that do not execute the app. This mechanism does not generate a sitemap or implement server-side rendering.
Hosting the generated routes
Serve a generated entry or real public file when it exists. For other
application paths, serve /index.html while preserving the requested browser
URL. A redirect to / would discard the location before the router reads it.
The default route owns the in-app not-found screen. The build does not create
a separate semantic 404.html, and choosing a fallback route cannot change
the status of an HTTP response already delivered by the server. Keep
missing asset responses separate from application fallback.
The generator also sets the entry documents' HTML base URL so nested entries can load the application bundle. Start with a domain-root deployment; a subdirectory deployment requires the browser paths, site URL, and hosting prefix to agree. See Targets, Build, and Deployment for the hosting configuration and its verification checklist.
Read more
- Components and Composition — page components, shared shells, and state ownership.
- State and Properties — reactive state that follows navigation.
- Animation and Motion — enter and exit lifecycles.
- Targets, Build, and Deployment — release output, direct links, and static hosting.
- Router, Route, RouteCard, and RouteModal reference and Link reference — individual component fields.
Layout and Responsiveness
The same note card might appear in a narrow phone view, beside another card on a desktop, or inside a small inspector panel. Its content needs an area to occupy and a few rules for adapting to that area.
Pax gives each node a local coordinate space. Sizes, positions, and transforms describe its relationship to its container; layout containers can assign smaller areas to their children. This chapter builds that spatial model, then uses it to arrange the cards from Components and Composition at different widths. Templates covers the syntax and settings rules used here.
The coordinate and size model
A node has a width and height, and a transform that places those bounds in its parent's space. The local origin is the upper-left corner; x increases to the right and y increases downward. Children use that local space, even when the whole group is moved or rotated.
The relevant area is usually the parent's content area. Padding reduces that area. A Stacker assigns each child a cell, which becomes the area the child uses for its own layout.
Use px for a concrete length and % for a proportion of the relevant axis.
These are logical layout units; 1px need not equal one physical display
pixel on a high-density screen. Width percentages use available width, and
height percentages use available height.
For example:
<Group x=24px y=24px width={100% - 48px} height=160px>
<Rectangle x=16px y=16px width={100% - 32px} height=128px
fill=rgb(246, 241, 230) corner_radius=12
/>
</Group>
The outer Group leaves 24 pixels on each side of its parent. The Rectangle
then leaves 16 pixels on each side of the Group. Its percentage width refers
to the Group, not the app window. Mixed-unit expressions retain those two
parts: 100% - 32px means “the available width minus 32 pixels.”
Ordinary omitted dimensions fill the available axis. Content measurement can supply omitted dimensions for text, native controls, and autosized containers, so make important constraints explicit. A fixed-height card should declare that height; a wrapping paragraph usually needs a width and room to grow. Read Text, Fonts, and Images for text measurement.
Position and alignment
Percentage positions also supply a default anchor on the node's own bounds.
That makes x=50% a convenient way to center an element, and x=100% a way
to align its right edge. Pixel positions default to a zero anchor.
For an untransformed 100-pixel-wide child in a 400-pixel-wide area:
| Position | Default anchor | Child's left edge |
|---|---|---|
x=0% | Left edge | 0px |
x=50% | Center | 150px |
x=100% | Right edge | 300px |
x=200px | Left edge | 200px |
Within the usual zero-to-100-percent range, the default behavior can be read as a percentage of the remaining travel: `left = (parent width - child width)
- percentage`. The same rule applies vertically.
<Group width=400px height=160px>
<Rectangle x=50% y=50% width=100px height=40px
fill=rgb(55, 120, 90)
/>
</Group>
This centers the rectangle on both axes. To align an element with a pixel inset from the far edge, combine units:
<Rectangle x={100% - 24px} y=24px width=100px height=40px
fill=rgb(55, 120, 90)
/>
The percentage part gives it a right-edge anchor, and the pixel part moves that edge 24 pixels inward. It does not place the left edge 24 pixels from the parent's right edge.
Explicit anchors
Set anchor_x or anchor_y when you want a particular point on the element
to meet a particular point in the parent:
<Rectangle x=50% y=24px anchor_x=0px width=100px height=40px
fill=rgb(55, 120, 90)
/>
Here the left edge begins halfway across the parent. An explicit anchor
takes precedence over the anchor inferred from position. Anchors are measured
against the node's own bounds: anchor_x=50% selects its center, while
anchor_x=12px selects a point 12 pixels from its left edge.
Be deliberate with anchors when placing content beyond an edge. For example,
y={100% + 8px} anchor_y=0px places the top of a child eight pixels below its
parent. The explicit anchor is important to that relationship.
Choose a container
A Group establishes a shared coordinate space. It does not automatically arrange siblings into rows. Use it for deliberately positioned content, layered backgrounds, badges, and pieces that should transform together.
A Frame provides similar grouping with clipping at its bounds by default.
Its corner_radius rounds the clipping boundary:
<Frame width=240px height=100px corner_radius=12>
<Rectangle x=200px y=20px width=120px height=60px
fill=rgb(55, 120, 90)
/>
<Rectangle width=100% height=100% fill=rgb(246, 241, 230) />
</Frame>
Only the portion of the green rectangle inside the Frame is visible. A Group of the same size would allow it to extend beyond the group's bounds. Compositing explains clipping and native/rendered content in more detail.
Rows and columns with Stacker
A Stacker assigns one cell to each participating child. Its default direction
is vertical; use StackerDirection::Horizontal for a row.
<Stacker width=100% height=160px
direction=StackerDirection::Horizontal gutter=16px
>
<NoteCard width=100% height=100% title="Field notes" progress=0.25 />
<NoteCard width=100% height=100% title="Sketchbook" progress=0.75 />
</Stacker>
The gutter separates the two cells. By default, they divide the remaining
main-axis space equally: in a 400-pixel-wide row with a 16-pixel gutter,
each gets 192 pixels. Each card's width=100% fills its own cell. It does
not request the entire row's width.
A child still has its own layout within its cell. A smaller child can use
x=50% or y=50% to align itself there. Nest horizontal and vertical
Stackers for more complex arrangements; a Stacker forms a single row or
column and does not automatically wrap.
To assign different cell sizes, supply sizes along the extending axis:
<Stacker width=400px height=160px
direction=StackerDirection::Horizontal gutter=16px
sizes=[Some(120px), None]
>
<Rectangle width=100% height=100% fill=rgb(220, 216, 206) />
<Rectangle width=100% height=100% fill=rgb(55, 120, 90) />
</Stacker>
The first cell is 120 pixels wide; the second receives the remaining 264
pixels. Supply an entry for each child when mixing fixed and flexible cells.
Keep the requested sizes and gutters within the available space. A child's
own width or height affects its content inside the assigned cell; sizes
controls the cell allocation.
Container placement and draw order are separate. Earlier siblings still render in front of later siblings. Keep backgrounds after their foreground content, as in the Frame example. See Element order.
Padding
padding_x adds equal inner spacing at the left and right;
padding_y does the same at the top and bottom. They change the area
available to children while leaving the node's outer bounds in place.
<Group width=240px height=160px padding_x=12px padding_y=16px>
<Text width=100% height=100% text="Room around an idea."
style={font_size: 18px, fill: rgb(32, 40, 48)}
/>
</Group>
The Text receives a 216-by-128-pixel area, offset by 12 and 16 pixels.
Percentage padding resolves against the corresponding outer dimension:
padding_x=10% reserves one tenth of the width on each side.
Padding applies to the child layout area. A background child that should cover the entire outer surface is often clearest as a sibling of a padded content Group. This separates the surface's bounds from its content inset.
Autosize
A fixed area gives children a space to fill. With autosize, content can
instead determine an omitted container dimension. This is useful for a stack
of differently sized notes or a label whose surrounding frame should grow.
<Stacker width=280px autosize=true gutter=12px>
<Group height=48px>
<Text x=12px y=12px width={100% - 24px} height=24px text="A short note" />
<Rectangle width=100% height=100% fill=rgb(246, 241, 230) />
</Group>
<Group height=96px>
<Text x=12px y=12px width={100% - 24px} height=72px
text="A longer note has more room for its contents."
/>
<Rectangle width=100% height=100% fill=rgb(220, 216, 206) />
</Group>
</Stacker>
This vertical stack has a width of 280 pixels and a measured height of 156: 48 plus 96, with one 12-pixel gutter. Its height is omitted so autosize can supply it. An explicit height would continue to constrain that axis.
For a Group or Frame, measurement includes the placed extent of its content.
A child beginning at x=200px with width 20 contributes an extent of 220
pixels from the origin. Content wholly before the origin does not add a
positive width. Padding contributes to the measured outer size as well.
Axis controls
With autosize=true, these are the defaults:
| Container | Measured axes |
|---|---|
| Vertical Stacker | Its extending axis: height |
| Horizontal Stacker | Its extending axis: width |
| Group and Frame | Both axes |
| Scroller | Scroll-content height, separately from the viewport |
| Link | Both axes from supplied content |
| Tooltip | Both axes from its trigger content |
autosize_x and autosize_y override those per-axis choices. For example,
a vertical stack can measure height while keeping a caller-supplied width.
Use autosize=true alongside Stacker's axis overrides. An explicitly
specified dimension still wins over measurement for that dimension.
Autosize needs a measurable starting point. A parent whose height comes only
from a child at height=100% leaves the calculation circular. Use concrete
child sizes or intrinsic measurements on the axis being measured. When the
axis cannot be resolved, current containers can fall back to top-down layout.
Empty measured content collapses to zero on managed axes.
Text and native controls can report measurements after initial layout, and
autosized ancestors react to those updates. Fonts, wrapping, and platform
controls influence the result. Check the settled layout with realistic
content rather than depending on its first frame. The
auto-sized-containers example
explores the available surfaces.
Responsive layout
Start with fluid relationships: percentage widths, deliberate insets, and cells that divide the available space. Add a breakpoint when the arrangement needs to change, such as two cards becoming too narrow to read beside each other.
Use the Notes and NoteCard declarations from Components, including the
advance handler. Replace lib.pax with this small responsive board:
<Group class="board" x=24px y=24px width={100% - 48px}>
<Text width=100% height=36px text="On the workbench"
style={font_size: 24px, fill: rgb(32, 40, 48)}
/>
<Stacker class="cards" y=64px width=100% gutter=16px>
<NoteCard width=100% height=100%
title={self.title} progress={self.progress}
/>
<NoteCard width=100% height=100%
title="Sketchbook" progress=0.75
/>
</Stacker>
<Button class="advance" width=160px height=36px
label="Advance" @button_click=self.advance
/>
</Group>
@settings {
.board { height: 296px }
.cards {
height: 160px
direction: StackerDirection::Horizontal
}
.advance { y: 248px }
if $viewport.width < 720 {
.board { height: 472px }
.cards {
height: 336px
direction: StackerDirection::Vertical
}
.advance { y: 424px }
}
}
At 720 pixels and above, the cards share a row. Below that threshold, they occupy two 160-pixel rows separated by the same 16-pixel gutter. The Button moves beneath them. Both layouts leave a 24-pixel outer inset.
These conditions change settings on the existing nodes. The cards retain
their identity and state while the window crosses the breakpoint. A
structural if that creates a different subtree has different lifetime
implications; see
Components.
The threshold belongs to this composition. Choose it from the space the content needs, then try widths just above and below it. Also test a short window: adapting a row into a column does not guarantee that all content fits vertically. Use a correctly sized Scroller when the content needs more space; Scrolling owns viewport/content sizing and input.
Viewport or component bounds?
$viewport.width and $viewport.height describe the mounted app's viewport
in logical pixels. In an embedded app, that is the embed's viewport, not the
entire surrounding page. Orientation helpers describe that same area.
They are useful for screen-level composition; see the complete
built-in globals.
A reusable component may receive only a fraction of that area. For example, a 320-pixel sidebar should not choose a wide layout merely because the app window is wide. Derive a property from the component's own bounds when its layout policy should follow the space its caller allocates.
For Notes, add pub compact: Property<bool> and initialize it with
compact: Property::new(false) in the existing custom default. Then add:
#![allow(unused)] fn main() { impl Notes { pub fn on_mount(&mut self, ctx: &NodeContext) { let bounds = ctx.bounds_self.clone(); let deps = [bounds.untyped()]; self.compact.replace_with(Property::computed( move || bounds.get().0 < 720.0, &deps, )); } } }
If Notes already has a mount method, add the computation there. Change the
settings condition to if self.compact; it now follows Notes' allocated
width. Read bounds in the component's lifecycle context here, rather than a
Button's event context. The latter would describe the Button. The
materials example
uses this bounds-derived pattern.
Avoid making the measured property depend on the very size rule it controls. A width supplied by the caller is a clear input for choosing the component's internal arrangement. Properties explains the dependency model.
Shared visual settings
Classes keep repeated visual choices together, and conditional settings can
adapt typography, spacing, or surface treatment as well as geometry.
Keep a card's common appearance in its component or shared settings; let the
containing layout own the arrangement. ImportSettings can share a settings
vocabulary across templates. See
Imported settings and
Drawing and Styling for themes, fills, and materials.
Text alignment controls content within the Text node's bounds. It does not
position that node in its parent. Use layout position and anchors for the
node, and style.align_horizontal, style.align_vertical, and
style.align_multiline for text within it. Text
covers the distinction in more detail.
Transforms and origins
rotate, scale_x/scale_y, and skew_x/skew_y transform a node and
its descendants around its anchor. Scaling changes the displayed geometry;
width and height remain the node's unscaled layout bounds.
<Group width=320px height=180px>
<Rectangle x=50% y=50% width=160px height=80px
rotate=-8deg scale=90% fill=rgb(55, 120, 90)
/>
</Group>
The default 50-percent anchors keep this rectangle centered as it turns and scales. Use explicit anchors when an object should rotate around another point. A parent's transform also transforms its children, so group pieces that should move together.
An assigned Stacker cell does not automatically rearrange its siblings to avoid visual overlap caused by a transform. Flow-space measurement excludes the full rotated, scaled, or skewed silhouette, so do not expect autosize to reserve all the space a transformed drawing can cover. Motion covers changes over time; Events explains converting input positions through transformed geometry.
Multi-Axis Common Properties
The anchor, scale, and skew shorthands accept a scalar for both axes
or a two-item list in [x, y] order:
<Group width=240px height=120px
x=50% y=50% anchor=[50%, 50%] scale=80% skew=[0deg, 4deg]
>
<Rectangle width=100% height=100% fill=rgb(220, 216, 206) />
</Group>
Axis-specific properties remain available. In the same settings layer, a
longhand such as scale_y takes precedence over the corresponding shorthand
axis. A longhand expression using $base sees that axis's shorthand value.
The wider settings precedence rules belong to
Templates.
Layout Role
layout_role=LayoutRole::Breakout excludes a child from its parent's flow
and measured content extent. It is useful for a badge or floating panel that
should remain near its owner without making the owner larger.
<Group width=160px autosize=true>
<Group y={100% + 8px} anchor_y=0px width=220px height=64px
layout_role=LayoutRole::Breakout
>
<Text x=12px y=12px width={100% - 24px} height=40px
text="More room for a thought."
/>
<Rectangle width=100% height=100% fill=rgb(246, 241, 230) />
</Group>
<Text width=160px height=32px text="Field notes" />
</Group>
The label determines the outer Group's measured height of 32 pixels. The floating panel begins eight pixels below it and does not enlarge that measurement. Earlier source order keeps the panel in front where it overlaps.
Breakout changes layout participation while retaining the normal ancestor
relationships. It still scrolls, clips, masks, renders, and hit-tests within
that tree. It does not create a portal above a clipping Frame or pin content
to the app window. Percentage sizes still use the relevant parent area.
The default LayoutRole::Default participates normally in flow and
measurement.
Read more
You can now predict an element's area, choose how it aligns within that area, and reshape a collection while keeping its state intact. Continue with Text, Fonts, and Images for content and measurement, Drawing and Styling for the visual vocabulary, and Scrolling for content larger than its viewport. Motion builds on these same layout relationships.
Text, Fonts, and Images
A heading, a paragraph, and an image give an interface much of its character. They also bring their own constraints: a sentence can wrap, a font can arrive after the first frame, and a photograph has proportions worth preserving.
This chapter covers those content decisions. It builds on the local bounds
and containers in Layout, with examples you can
place in a component's template. Keep use pax_kit::*; in its Rust file.
Display text
Text displays a string through the target's native text system. On web,
that means DOM text; macOS, iOS, and iPadOS use the native text rendering
provided by their chassis. Pax positions and composites that content alongside
drawings and images.
Use style to set the type's appearance. A title and a short description can
share a family and color while differing in size and weight:
<Group x=24px y=24px width={100% - 48px} height=180px>
<Text width=100% height=40px text="Field notes" class="heading" />
<Text y=56px width=100%
text={"Collect a small observation. Give it a title, " +
"then leave room for the next idea."}
class="body"
/>
</Group>
@settings {
.heading {
style: {
font: {family: "Arial", weight: 700}
font_size: 28px
fill: rgb(32, 40, 48)
}
}
.body {
style: {
font: "Arial"
font_size: 18px
fill: rgb(72, 80, 88)
}
}
}
text={self.title} can replace a literal when the content comes from state.
The same reactive bindings used for layout work here; see
Properties and
PAXEL.
TextStyle groups font, size, fill, underline, and alignment settings. Use
pixel sizes such as 18px for font_size: the current text patch expects a
pixel value. Use a solid color for fill. Although its Rust type is Fill,
native text currently receives only the first stop's color from a gradient.
Typography classes belong to their component's settings. For a shared type
scale across components, see Imported settings
and ImportSettings. Ancestor text styling is not a substitute for explicitly
applying the intended style to each Text node.
Size and wrap text
A paragraph usually needs a width and an omitted height:
<Text x=24px y=24px width={100% - 48px}
text={"A useful note can be one sentence or several. " +
"Let the available width determine the line breaks."}
style={font: "Arial", font_size: 18px, fill: rgb(32, 40, 48)}
/>
wrap defaults to true. With a width constraint, the native renderer breaks
lines and reports the height it needs. Pax uses that measurement for the
omitted dimension. Supplying both width and height gives Text a fixed box;
it does not reduce the font size to make everything fit.
Measurements can change when the string, width, style, or loaded font changes.
Allow the layout to settle before relying on a measured size. A following
element at a fixed y will not move just because the paragraph grew. Use an
autosized container when surrounding content should follow it; see
Autosize.
For a single-line label, wrap=false disables automatic line breaking.
clip=true cuts off content outside the Text's bounds. Clipping does not
insert an ellipsis. By default clip is false, so text may paint beyond its
box; an ancestor Frame can still clip it.
<Text x=24px y=24px width=220px height=28px wrap=false clip=true
text="A long label with more to say than this row can hold"
style={font: "Arial", font_size: 18px, fill: rgb(32, 40, 48)}
/>
Align content within its box
Layout positions the Text node. TextStyle aligns the content inside it:
| Setting | Responsibility | Values |
|---|---|---|
align_horizontal | Places the text block horizontally inside the node | TextAlignHorizontal::Left, Center, Right |
align_vertical | Places the text block vertically inside the node | TextAlignVertical::Top, Center, Bottom |
align_multiline | Aligns lines within the text block | TextAlignHorizontal::Left, Center, Right |
For a centered caption, set all three deliberately:
<Text width=240px height=100px text="A place to begin, and room to continue."
style={
font: "Arial"
font_size: 20px
fill: rgb(32, 40, 48)
align_horizontal: TextAlignHorizontal::Center
align_vertical: TextAlignVertical::Center
align_multiline: TextAlignHorizontal::Center
}
/>
The default alignments are left, top, and left. Text metrics and line breaks can differ between platforms even with the same declared size. Check your longest content and the scripts your application needs on each shipping target.
Font values
A string names a locally available family: font: "Arial". It selects normal
style and weight, with no font download URL. Availability belongs to the
workstation or device; a missing family falls back through the native renderer.
A named object adds modifiers:
<Text width=280px height=48px text="An observation worth keeping"
style={
font: {family: "Arial", style: FontStyle::Italic, weight: 700}
font_size: 22px
fill: rgb(32, 40, 48)
}
/>
The fields are family, optional url, style, and weight, in any order.
Styles are FontStyle::Normal, Italic, or Oblique. Numeric weights are
100 through 900 in increments of 100; FontWeight variants such as
Normal, Medium, and Bold are also accepted. A weight selects a face or
renderer approximation; it does not supply a missing font file. Oblique is
accepted by the API, but the current Apple text path does not apply an oblique
transformation.
Supplying family without url keeps the URL empty. Omitting the entire font
uses Pax's current default: Roboto with a Google Fonts URL. Set a family or
source explicitly when offline behavior and repeatable typography matter.
Font::Web(family, url, style, weight) remains the explicit longhand; the
string and named-object forms describe the same public font type.
Load a font file on web
For a project-owned web font, put the licensed font file in assets/fonts/
and reference it through url. For example, with a file named
NotesRegular.ttf:
<Text width=320px height=48px text="A typeface for these notes"
style={
font: {family: "Notes", url: "assets/fonts/NotesRegular.ttf", weight: 400}
font_size: 24px
fill: rgb(32, 40, 48)
}
/>
Here Notes is the family name registered with the browser for that file.
Provide the actual font asset before trying the snippet. A direct remote
font-file URL uses the same web loading path, subject to the server's access
rules and the application's network policy.
Google Fonts stylesheet URLs containing fonts.googleapis.com/css receive
special handling. Other nonempty URLs are treated as font files, so an
arbitrary CSS stylesheet URL is not interchangeable with a font-file URL.
The native Apple loader also handles direct remote font files and the Google
Fonts CSS path, registering decoded fonts with the platform. The relative
assets/fonts/... recipe above is web-specific: the current native Web-font
loader does not resolve it into the application asset bundle. Choose an
available native family or verify your remote font source on macOS, iOS, and
iPadOS. Match the native font's actual family name; browser aliases alone do
not establish that name on Apple targets.
Font loading is asynchronous. A fallback may appear first and then change the paragraph's metrics. Include first-load and unavailable-font cases in your checks, and make sure the files you ship or host permit that use.
Selection and editing
Text is selectable by default and non-editable by default. selectable=false
is useful for decorative labels that should not start a selection gesture.
Selection is target-dependent: the current iOS/iPadOS path uses an interactive
selection view for clipped text, while ordinary unclipped, non-editable text
uses its static rendering path. Test selection on the device rather than
assuming the property guarantees identical interaction everywhere.
editable=true makes Text a native editing surface. Edits update that Text
node's text property. Use an explicit two-way binding when the edited value
should belong to the containing component:
<Text x=24px y=24px width=280px height=40px
editable=true text=bind:self.title
style={font: "Arial", font_size: 24px, fill: rgb(32, 40, 48)}
/>
<Text x=24px y=80px width=280px text={"Current title: " + self.title}
style={font: "Arial", font_size: 16px, fill: rgb(72, 80, 88)}
/>
This uses the title: Property<String> on Notes from
Properties. An ordinary
text={self.title} is a value binding and does not establish write-through
to the owner's property. See Inputs and state ownership
for that distinction.
For a conventional form field, start with Textbox. Its input/change events give the application explicit places for validation and committed actions. See Events and Native Controls for forms and focus behavior.
Markdown content
Set markdown=true for authored content with inline emphasis and other
Markdown formatting:
<Text width=300px markdown=true
text="Keep **one observation** and *one question*."
style={font: "Arial", font_size: 18px, fill: rgb(32, 40, 48)}
/>
Web and Apple targets use different Markdown renderers, so start with simple
formatting and verify anything richer on your targets. Treat this as a
trusted-content surface: the web path inserts rendered markup and does not
provide a sanitization boundary for arbitrary user input. Leave markdown
false for plain strings from users.
Image sources
Keep project-owned images in the project's assets/ directory. Pax copies
that directory into the build output. Start with a bundled PNG or JPEG and
an explicit display area:
<Image x=24px y=24px width=160px height=100px
source="assets/spaceship.png" fit=ImageFit::Fit
/>
For this snippet, copy spaceship.png from the canonical
examples/src/space-game/assets/ directory into your project's assets/.
You can substitute your own image and keep the same layout. The path is
relative to the app's asset layout, not the directory containing its .pax
template.
Image is drawn through Pax's GPU/canvas rendering path. Its intrinsic pixel
dimensions determine its proportions, but do not automatically size the
node. Set width and height for the area the image should occupy.
Image assets load asynchronously. Once decoding finishes, Pax schedules a redraw of the mounted image.
A string is shorthand for ImageSource::Url. source={self.image_path}
works the same way when image_path is a Property<String>; updating the
property changes the requested image. The explicit
ImageSource::Url("assets/spaceship.png") is also valid. An omitted source
uses ImageSource::Empty.
Despite the Url name, arbitrary remote URLs are not currently a portable
Image source. The web canvas loader prefixes the app's document directory,
and the Apple canvas loaders resolve bundled assets. Use app-relative assets
for this path. Web NativeImage has a separate browser URL path, described below.
Fit an image into its bounds
Choose whether preserving the whole image, covering the area, or stretching it matters most:
fit | Result |
|---|---|
ImageFit::Fit (default) | Preserves proportions and shows the whole image; may leave unused space |
ImageFit::Fill | Preserves proportions and covers the bounds; crops the excess |
ImageFit::Stretch | Maps the image directly to both dimensions; may distort it |
Image centers the fitted result and clips it to its own rectangular bounds. With a square source in a wide box, Fit leaves space at the sides, Fill crops the top and bottom, and Stretch makes the subject wider:
<Group width=400px height=80px>
<Image width=128px height=80px
source="assets/spaceship.png" fit=ImageFit::Fit />
<Image x=136px width=128px height=80px
source="assets/spaceship.png" fit=ImageFit::Fill />
<Image x=272px width=128px height=80px
source="assets/spaceship.png" fit=ImageFit::Stretch />
</Group>
Use a Frame around an Image for rounded clipping; an Image's own fit policy only defines how pixels occupy its box. Read Compositing for clipping and masks.
Raw pixel data
Rust can provide an ImageSource::Data(width, height, bytes) for generated
pixels. The bytes are decoded RGBA values, four per pixel, and must contain
exactly width * height * 4 entries. Compressed PNG/JPEG file bytes are not
that representation.
The same constructor can be demonstrated with two pixels in a template:
<Image width=240px height=120px fit=ImageFit::Stretch
source=ImageSource::Data(2, 1, [55, 120, 90, 255, 246, 241, 230, 255])
/>
The four values in each group are red, green, blue, and alpha, from 0 to 255. For substantial images, keep pixel generation or decoding in Rust and bind the result, instead of embedding a large byte array in the template.
Native images and target boundaries
NativeImage uses a platform image element instead of the canvas image
path. It has a string url property rather than an ImageSource:
<NativeImage x=24px y=24px width=160px height=100px
url="assets/spaceship.png" fit=ImageFit::Fit
/>
On web, this creates a browser image element, so app-relative paths and browser-supported remote URLs can be used. On macOS, iOS, and iPadOS, the current native widget loads local filesystem paths or file URLs. Do not assume the web-relative asset snippet resolves in the native app bundle. PhotoPicker provides platform image handles for this use; see Photo selection.
The fit values are shared with Image. Web and iOS/iPadOS implement contain, cover, and stretch behavior. The current macOS NativeImage maps Fill to stretching; use the canvas Image path when proportional fill cropping is required there.
Format support belongs to the active decoder. Browser image support does not establish native target support, and loading a format through Image does not promise animated playback of it. Verify actual assets on each target. The canvas path uploads decoded pixels; large source images still cost memory even when their display bounds are small.
Neither current image component exposes a public alt property. Keep
important information available as text, and give image-driven actions a
clear control label. A visible caption is useful content, but is not a claim
that an image has an associated screen-reader description. Native rendering
alone does not establish complete accessibility; test the interaction and
reading order with the platform's assistive tools.
Read more
Continue with Drawing and Styling for shapes, fills, strokes, and visual surfaces. Read Compositing for mixing native and rendered content, and Native Controls for input, selection, and form workflows.
The API references list the remaining details for Text and fonts, Image, and NativeImage.
Drawing and Styling
A few drawing decisions can give a small interface a recognizable character: a warm surface, a fine outline, a generous corner, a line that guides the eye. Pax's vector elements let you make those decisions in the same template that positions your text and controls.
This chapter builds on Layout and
PAXEL. The examples can go inside a component's
template, with use pax_kit::*; in its Rust file. Start with ordinary shapes
and paint; the later sections introduce custom paths, SVG, and lighting.
Compose a surface
Here is a small surface for the Field notes content from the previous chapter:
<Group x=24px y=24px width=280px height=156px>
<Text x=24px y=24px width=232px height=36px text="Field notes"
style={font: "Arial", font_size: 24px, fill: rgb(36, 54, 47)} />
<Line x=24px y=80px width=232px height=1px
x1=0px y1=0px x2=100% y2=0px
stroke={color: rgb(152, 168, 146), width: 1px} />
<Ellipse x=24px y=108px width=12px height=12px fill=rgb(67, 112, 86) />
<Rectangle width=100% height=100% corner_radius=18
fill=rgb(237, 241, 226)
stroke={color: rgb(178, 192, 168), width: 1px} />
</Group>
The rectangle is last because it sits behind the other elements. Keep this foreground-first order when adding a highlight or a background; see Element ordering.
Rectangle fills its bounds; Ellipse fits an oval inside them. Equal width and height give Ellipse a circle. Line connects two local endpoints, while Path describes a longer sequence of segments and curves. They share layout properties with other elements, so percentages, transforms, and reactive bindings apply to drawings too.
A Group organizes these elements but does not paint a background of its own. Likewise, rounding a Rectangle changes that rectangle's geometry; it does not clip its siblings. Use a Frame when content needs to stay inside a rounded boundary.
Color and transparency
Use rgb for an opaque color and rgba when its paint should be translucent.
RGB channels and an integer alpha use the range 0–255. Percent channels are
also accepted. These two rectangles use equivalent half-opacity paint:
<Rectangle x=24px y=24px width=120px height=72px
fill=rgba(56, 120, 91, 50%) />
<Rectangle x=160px y=24px width=120px height=72px
fill=rgb(56, 120, 91) opacity=50% />
For an integer alpha, 255 is fully opaque and 128 is approximately half.
Use 50% when you mean an exact half: a decimal 0.5 in rgba is not a
normalized alpha value. Element opacity, by contrast, accepts 0.5 or
50%. Paint alpha and element opacity multiply, so applying both halves
leaves one-quarter opacity for that paint.
hsl(150deg, 30%, 45%) describes hue, saturation, and lightness;
hsla(150deg, 30%, 45%, 50%) adds alpha. Named colors such as WHITE, BLACK,
BLUE, and TEAL are convenient palette values. The colored names use Pax's
palette—RED, for example, is not a synonym for rgb(255, 0, 0). Use explicit
channels when matching a design precisely. TRANSPARENT provides clear paint.
For a vector, fill paints the interior and stroke paints its outline.
An omitted fill uses the default SLATE color; an omitted stroke has zero
width. Set fill=TRANSPARENT when you want only an outline.
Opacity on a container also affects its descendants. The behavior of
overlapping children and native elements belongs to
Compositing; do not assume a group fades as a
single flattened image. Text's style.fill has its own native-rendering
limits, covered in Text and fonts.
Outlines and corners
Strokes
A stroke combines a color, pixel width, cap, and join. It is centered on the path, so a 4px outline extends about 2px to either side of its centerline. Leave room for that extension near a clipping edge.
<Line x=24px y=24px width=240px height=40px
x1=8px y1=20px x2={100% - 8px} y2=20px
stroke={
color: rgb(56, 120, 91)
width: 8px
cap: StrokeCap::Round
}
/>
StrokeCap::Butt ends at the endpoint, Round adds a semicircle, and Square
extends a squared end by half the stroke width. Butt is the default. Closed
contours have no exposed endpoints, so cap selection is useful for Line and
open Path contours.
For joined segments, choose StrokeJoin::Miter, Round, or Bevel. Miter is
the default and extends edges toward a point; Round softens the turn; Bevel
cuts the corner across. A stroke's paint is a Color, not a gradient Fill.
Use pixel widths such as 2px; percentage stroke widths are not supported by
the current vector renderers.
Corner radii
Rectangle accepts one to four corner values. They are unitless numbers representing local pixel radii. A single number rounds every corner:
<Rectangle x=24px y=24px width=240px height=100px
corner_radius=[24, 8, 24, 8] fill=rgb(220, 232, 211) />
The list expands clockwise from the top-left:
| Input | Top-left | Top-right | Bottom-right | Bottom-left |
|---|---|---|---|---|
12 or [12] | 12 | 12 | 12 | 12 |
[12, 6] | 12 | 6 | 12 | 6 |
[12, 6, 3] | 12 | 6 | 3 | 6 |
[12, 6, 3, 1] | 12 | 6 | 3 | 1 |
Use a named object when the corner names make an asymmetric shape clearer:
corner_radius={top_left: 12, top_right: 6, bottom_right: 3, bottom_left: 1}.
The explicit CornerRadii { ... } form is also available. Size, anchoring,
and transforms remain the responsibilities described in Layout.
Gradients
A gradient varies the fill across a shape. @gradient lists the colors at
positions along it; the renderer blends between those stops:
<Rectangle x=24px y=24px width=280px height=140px corner_radius=18
fill=@gradient {
linear: {
start: [0%, 0%]
end: [100%, 100%]
}
0%: rgb(237, 241, 226)
55%: rgb(173, 205, 173)
100%: rgb(67, 112, 86)
}
/>
Points are [x, y] pairs in the shape's local coordinate space. Here the
gradient runs from the top-left to the bottom-right. Percent coordinates
follow the shape's bounds as it resizes. Use at least two stops in ascending
order and write their positions as percentages; the Piet fallback requires
percentage stops even though the GPU renderer can also interpret pixels.
Omitting the linear block gives a left-to-right gradient, from [0%, 0%]
to [100%, 0%]:
<Rectangle x=24px y=24px width=280px height=80px
fill=@gradient {
0%: rgb(237, 241, 226)
100%: rgb(67, 112, 86)
}
/>
Put transparency in each stop's color, such as rgba(255, 255, 255, 0).
There is no separate stop-opacity field. A translucent gradient over a
surface can supply a highlight while preserving the underlying color.
Radial gradients are also available through a radial block with start,
end, and radius. Their geometry currently differs between the GPU and
Piet renderers: GPU uses the start-to-end vector scaled by radius; Piet uses
origin/center points and a radius. In particular, equal start/end points
collapse the GPU gradient's axis. Treat radial fills as backend-sensitive
and verify their appearance on your shipping targets; the linear examples
above are the starting point for this chapter.
Reusable visual settings
Choose a small vocabulary for your interface: perhaps a paper surface, a quiet outline, an accent color, and two corner sizes. Classes can name those roles so that repeated elements stay consistent:
<Rectangle x=24px y=24px width=120px height=80px class="paper" />
<Rectangle x=160px y=24px width=120px height=80px class="paper"
fill=rgb(219, 233, 212) />
@settings {
.paper {
fill: rgb(237, 241, 226)
stroke: {color: rgb(178, 192, 168), width: 1px}
corner_radius: 16
}
}
Both surfaces share an outline and corner shape; the second supplies its own fill inline. Class names describe a role here, which makes them useful when the palette changes. Keep typography roles in the same visual system, using TextStyle on the Text elements that need them.
When several components need those choices, use a theme component through
ImportSettings. Templates owns
the provider setup, scope, and override order. A child component imports the
settings needed by its own template; placing a theme above it does not
automatically style its internals.
The canonical examples/src/runtime-settings-themes example separates color,
typography, and corners into providers and switches them with ordinary
reactive state. It is a useful next step when one shared class has grown into
a theme. Keep event handlers responsible for state changes and let bindings
select the visual values, as described in PAXEL.
Paths and SVG
Describe a path
Path's elements property holds commands and points. Start with a Point,
then add a segment command followed by its endpoint:
<Path x=24px y=24px width=280px height=120px fill=TRANSPARENT
stroke={color: rgb(56, 120, 91), width: 4px, cap: StrokeCap::Round}
elements=[
PathElement::Point(0%, 75%),
PathElement::Cubic(25%, 0%, 75%, 100%),
PathElement::Point(100%, 25%)
]
/>
The Cubic supplies two control points, which pull the curve toward them; the following Point supplies its destination. Coordinates belong to the Path's own bounds. Percentages reshape the curve with those bounds; pixels give fixed local distances.
| Command | Meaning |
|---|---|
Point(x, y) by itself | Move to a point and begin a new contour |
Line, then Point(x, y) | Draw a straight segment |
Quadratic(cx, cy), then Point(x, y) | Draw a curve with one control point |
Cubic(c1x, c1y, c2x, c2y), then Point(x, y) | Draw a curve with two control points |
Close | Connect back to the contour's starting point |
These entries all use the PathElement:: prefix in a template. Close a
contour deliberately when drawing a filled shape:
<Path x=24px y=24px width=160px height=100px
fill=rgb(220, 232, 211)
stroke={color: rgb(56, 120, 91), width: 3px, join: StrokeJoin::Round}
elements=[
PathElement::Point(50%, 8%),
PathElement::Line, PathElement::Point(92%, 92%),
PathElement::Line, PathElement::Point(8%, 92%),
PathElement::Close
]
/>
For data-driven drawings, keep a Property<Vec<PathElement>> in Rust and
bind it to elements. Updating the property changes the drawing through
the usual reactive loop. Rust helpers Path::start, Path::line_to, and
Path::curve_to can assemble a command list; the last makes a quadratic
curve. See Properties
for collection updates.
Path geometry may extend outside its layout bounds. Use Frame or Mask when
you want clipping, rather than relying on width and height to crop it.
smoothing=PathSmoothing::Light or Strong can soften polyline runs;
the default None preserves the authored geometry. Smoothing changes the
shape, so inspect corners and lettering after enabling it.
Reveal a stroke
draw_start and draw_end select a range along the total path length. This
line shows its first half:
<Path x=24px y=24px width=280px height=40px fill=TRANSPARENT
stroke={color: rgb(56, 120, 91), width: 6px, cap: StrokeCap::Round}
draw_start=0% draw_end=50%
elements=[
PathElement::Point(0%, 50%),
PathElement::Line, PathElement::Point(100%, 50%)
]
/>
The default range is 0 to 1, showing the full stroke. 0.5 and 50% mean the
same progress; values are clamped to this range. An empty or reversed range
shows no stroke. The fill remains intact: these properties reveal the
outline, so use a transparent fill for a pen-like drawing effect.
On the GPU renderer, the revealed range cuts across the existing stroke; round caps at the original endpoints do not add a rounded tip at that cut.
Bind the range to application state or animate it with a timeline. The
path-drawing example combines authored curves and imported lettering;
Animation and Motion owns the timing and playback
model. Handwriter is a higher-level component that turns text into paths
using bundled stroke fonts, with the same draw-range controls. See its
API reference for font and text options.
Bring in an SVG
SVG import converts supported vector artwork into Pax Paths at build time. First validate the source from your project directory:
pax-cli svg-import assets/signature.svg
For a concrete source to try, copy
examples/src/path-drawing/assets/svg/signature-strokes.svg to that location.
The command reports its viewBox, generated path count, and warnings. Review
those warnings and compare the result visually before adopting the artwork.
The importer requires a valid viewBox and supports paths and polygons, line and Bézier commands, transforms, solid fills, and strokes. It is a bounded subset of SVG: convert shapes and text to paths before importing; convert arc commands to curves. Gradient paints are rejected. Filters, masks, and other unsupported elements are not reproduced. Complex SVG styling and compositing need a separate fidelity check.
To keep the SVG as your source, declare a component in Rust. This example
can live in src/signature.rs:
#![allow(unused)] fn main() { use pax_kit::*; #[pax] #[custom(Default)] #[svg("assets/signature.svg")] pub struct Signature { pub draw_start: Property<UnitValue>, pub draw_end: Property<UnitValue>, } impl Default for Signature { fn default() -> Self { Self { draw_start: Property::new(UnitValue::Unitless(0.0.into())), draw_end: Property::new(UnitValue::Unitless(1.0.into())), } } } }
Declare pub mod signature; and use signature::Signature; in src/lib.rs,
then place the component in the template:
<Signature x=24px y=24px width=320px height=80px />
The example preserves the source's 4:1 aspect ratio. Imported coordinates scale into the component's allocated bounds; use a matching ratio when you want the original shape proportions. Emitted stroke widths remain pixel values, so inspect their weight at the size you intend to display. The draw-range properties let the generated strokes participate in the same reveal workflow as a hand-authored Path.
To edit the generated Pax instead, eject a component:
pax-cli svg-import assets/signature.svg --component Signature --out src/signature
This writes src/signature.rs and src/signature.pax; use that pair instead
of the #[svg] declaration above. Existing files are protected unless you
explicitly pass --force. After ejection, the Pax file is editable source:
changes to the original SVG do not automatically update it. To inspect the
generated template without writing files, use --stdout.
Lighting and materials
Vector materials can respond to authored lights on the GPU renderer. A LightSource describes illumination; a Material describes how a surface responds. LightFrame keeps a local light from spilling into neighboring component instances:
<LightFrame x=24px y=24px width=280px height=160px>
<Rectangle width=100% height=100% corner_radius=20
fill=rgb(67, 112, 86) material={Material::glossy(0.6)} />
<LightSource x=64px y=40px width=1px height=1px
z=90px radius=240px intensity=1.5 color=rgb(255, 231, 192) />
</LightFrame>
LightSource is a non-rendering resource; it does not draw a visible lamp.
Its point-light position uses its local center plus the authored depth.
Use pixel values for radius and z. Depth here affects lighting, not the
element order established by the template. Directional lights are also
available through shape=LightShape::Directional and a direction vector.
The default vector material is matte. Material::glossy(...) and
Material::metallic(...) adjust the response; Material::unlit() preserves
the authored paint independently of lights. Material::emissive(...) adds
color to its own surface; it does not turn the shape into a light source
for its neighbors. The material reference
describes explicit response coefficients.
LightFrame containment is one-way: outside lights can enter, while lights
inside cannot affect parents or sibling frames. Canvas-layer reachability
still applies, and AmbientLight is layer-wide rather than confined by
LightFrame. A primitive without eligible direct lights or authored ambient
keeps its unlit appearance.
This feature is backend-dependent. GPU/WGPU rendering supports the material response, with a current limit of eight active direct lights in each canvas layer's lighting set, shared across its LightFrames. The Piet fallback draws the ordinary fills and strokes without that response. Native Text, native controls, and bitmap Images are not lit vector materials. Texture maps, cast shadows, and a 3D camera are not part of this API. Keep information legible when lighting is absent.
Try Materials in Compositing and Effects to compare surface responses as scrolling moves a light through the scene. The source tabs show the material parameters and scroll-driven light position.
Read more
Continue with Native Controls to add familiar inputs, Animation and Motion to change these values over time, and Compositing to combine clipping, masking, and native content.
The API references cover Rectangle and corners, Ellipse, Line, Path, colors, and light resources.
Accessibility and Native Controls
Native controls bring familiar editing and selection behavior into a Pax interface: entering a note, choosing an option, moving a slider, or opening a photo library. They share the scene's layout with your text, drawings, and images, while the browser or operating system supplies their underlying controls.
This chapter builds on Properties and Event Handling. It starts with a small form, then covers keyboard interaction, accessibility, and photo selection. The current support section identifies differences to check when shipping on web, macOS, iOS, or iPadOS.
A small editable form
Let's add a settings form to Field notes. A name, a location preference, a
visibility choice, and a detail level all belong to the component. Each
control edits one of those properties through bind:. A Save button calls
Rust when the reader is ready to apply their choices.
In src/lib.rs:
#![allow(unused)] fn main() { use pax_kit::*; #[pax] #[main] #[file("lib.pax")] pub struct Example { pub name: Property<String>, pub include_location: Property<bool>, pub visibility: Property<u32>, pub detail: Property<f64>, pub message: Property<String>, } impl Example { pub fn save(&mut self, _ctx: &NodeContext, _event: Event<ButtonClick>) { let name = self.name.get(); if name.trim().is_empty() { self.message.set("Give this notebook a name first.".into()); return; } let visibility = match self.visibility.get() { 1 => "shared", _ => "private", }; let location = if self.include_location.get() { "on" } else { "off" }; self.message.set(format!( "{}: {}, location {}, detail {:.1}.", name.trim(), visibility, location, self.detail.get() )); } } }
In src/lib.pax:
<Group x=24px y=24px width={100% - 48px} height=452px>
<Text class="label" width=100% height=28px text="Notebook name" />
<Textbox y=32px width=100% height=40px
text=bind:self.name placeholder="e.g. Moss and rain" />
<Checkbox y=92px width=24px height=24px
checked=bind:self.include_location />
<Text class="label" x=36px y=90px width={100% - 36px} height=28px
text="Include location" />
<Text class="label" y=140px width=100% height=28px text="Visibility" />
<Dropdown y=172px width=100% height=40px
options=["Private", "Shared"] selected_id=bind:self.visibility />
<Text class="label" y=236px width=100% height=28px
text={"Detail: " + self.detail} />
<Slider y=268px width=100% height=32px
min=0.0 max=1.0 step=0.1 value=bind:self.detail />
<Button y=324px width=180px height=44px label="Save notebook"
color=rgb(56, 100, 78) hover_color=rgb(43, 79, 60)
@button_click=self.save />
<Text class="label" y=388px width=100% height=64px text={self.message} />
</Group>
@settings {
.label {
style: {font: "Arial", font_size: 16px, fill: rgb(36, 54, 47)}
}
}
Try saving before entering a name, then change the fields and save again. The detail label updates while the slider moves. The message changes only when Save runs. This sample reports the choices on screen; a real app would also persist or submit them from Rust.
bind:self.name connects the Textbox's editable property to the component's
property. Typing updates both. A one-way expression such as text={self.name}
supplies a value to the control without making it an editor for the parent
property. Bind directly to state; put formatting and derived display values
in separate expressions. See Two-way bindings
for the underlying model.
The visible labels above help someone looking at the form. They are separate Text elements: Pax does not currently turn that proximity into a semantic label association for assistive technology. Keep the accessibility boundary in mind when adapting the form.
Choose a control
These controls are available through use pax_kit::*;. The event column lists
control-specific bindings; ordinary pointer events are covered in
Event Handling.
| Control | Editable property or content | Control event |
|---|---|---|
| Button | label supplies its visible title | @button_click, with Event<ButtonClick> |
| Textbox | text: String | @textbox_input while editing; @textbox_change when the platform commits a change |
| Checkbox | checked: bool | @checkbox_change, with event.checked |
| Slider | value: f64, bounded by min and max | @slider_change, with event.value |
| Dropdown | options: Vec<String> and selected_id: u32 | Bind selected_id; there is no dedicated public dropdown-change event |
| RadioList | options: Vec<String> and selected_id: u32 | Bind selected_id; there is no dedicated public radio-list-change event |
selected_id is a zero-based index into options, despite its name. The
first option is index 0. Keep the index valid when replacing or reordering
the options; map it to your application's own identifier in Rust when needed.
On both web and Apple chassis, native input updates the relevant control property before calling its TextboxInput, CheckboxChange, or SliderChange handler. A bound parent property therefore already contains the new value. Use an event handler for validation or side effects, without writing the same value back just to maintain the binding.
On web, @textbox_input follows the browser's input event, while
@textbox_change follows change. Apple text controls report input during
editing and change when editing ends. Use input for immediate feedback and
an explicit Button for a submission action; do not assume a change event
means Enter was pressed. Likewise, a slider-change handler can run repeatedly
during a drag, so keep expensive work out of that immediate path.
Multiline text and styling
For a longer note, add a Property<String> named notes and use
multiline=true:
<Textbox x=24px y=24px width={100% - 48px} height=140px
multiline=true text=bind:self.notes
background=rgb(249, 247, 239)
stroke={color: rgb(155, 170, 153), width: 1px}
corner_radius=8
style={font: "Arial", font_size: 18px, fill: rgb(36, 54, 47)} />
Give a multiline editor enough height for its contents. A placeholder is available for single-line Textbox on web and Apple, and multiline Textbox on web; the current Apple multiline views do not display one. Keep a persistent visible label when the field needs an explanation.
Native controls expose their own styling properties. Button uses color,
hover_color, outline, corner_radius, and style; Textbox uses
background, stroke, outline, corner_radius, and style. Checkbox has
separate unchecked and checked backgrounds, while Slider exposes an accent.
Consult the control's API for its exact property names and defaults. The
platform still influences its appearance, especially focus and hover states.
An ordinary Button renders its own label and surface. For a custom composition, build a component and handle its interactions deliberately; adding a click handler to a drawing does not supply a button's keyboard and assistive-technology behavior. PhotoPicker's slotted design below is a specific API for a custom visible affordance.
ComboBox, Tabs, and dialog components combine Pax elements and controls into larger patterns. Their presence in the library does not imply an operating-system dialog or a complete accessible widget contract.
Focus and keyboard interaction
Native text fields own editing, selection, and keyboard input while focused.
To request focus when a Textbox appears, set focus_on_mount=true. This can
be useful for a deliberate editing action; avoid taking focus automatically
from someone already navigating the page. On a mobile device, focusing a
field can also bring up the software keyboard.
In the web chassis, native controls participate in browser keyboard navigation. The current implementation derives their tab indices from visual stacking order; Pax does not yet offer an independent public tab-order property. Changes to element order, conditional content, or compositing can therefore affect the path through the form. Walk that path with a keyboard in the actual application.
While a native element has focus on web, the chassis leaves its keyboard
input with that element. A component's @key_down is not a text-entry hook
or a way to make a drawn widget focusable. See
Keyboard events for the app-level
event boundary.
Accessibility today
Pax's native text and controls provide a starting point for accessibility. On web, a Button is a browser button, Textbox uses an input or textarea, Dropdown uses a select, and Checkbox and Slider use input elements. The Apple chassis uses UIKit and AppKit controls, with custom styling and some composed implementations.
The broader accessibility model is still incomplete. Public authoring APIs do not yet provide a general contract for accessible names, descriptions, roles, label associations, or a reading order independent of visual layout. Image and NativeImage currently expose no alternative-text property. A custom-drawn control has no automatically generated semantic counterpart. Native backing alone also does not establish that every state is announced correctly on every platform.
For an application, check the actual tasks a person must complete:
- Navigate the whole interaction with a keyboard, including opening and dismissing transient UI. Check where focus goes afterward.
- Use the target's screen reader to inspect names, values, selected states, and reading order. Check whether validation messages are discoverable.
- Keep instructions and errors visible in text, and check that zoomed or larger text still fits the layout. Avoid communicating a state through color alone.
These checks can reveal a requirement that needs additional engine work. If your product depends on a particular accessibility contract, verify it early against the current implementation. This chapter makes no blanket screen-reader or standards-conformance claim.
PhotoPicker
PhotoPicker lets someone select images for the application. Its children draw the visible affordance; a transparent native control above them opens the platform picker. Keep those children simple and give the whole picker a usable size:
<PhotoPicker x=24px y=24px width=240px height=48px
source=PhotoPickerSource::Library allow_multiple=true
include_bytes=false max_bytes_per_photo=26214400
@photo_picker_change=self.choose_photos>
<Text width=100% height=100% text="Choose photos"
style={
font: "Arial", font_size: 18px, fill: WHITE
align_horizontal: HorizontalAlign::Center
align_vertical: VerticalAlign::Center
} />
<Rectangle width=100% height=100% corner_radius=8
fill=rgb(56, 100, 78) />
</PhotoPicker>
The default accept filter is "image/*". On web it is passed to the file
input; Apple's current pickers select images without applying a custom
accept filter. Use allow_multiple=false when only one existing photo is
needed. Camera capture produces one photo at a time.
Handle selection and partial results
The completion event includes status, an optional message, request_id,
and a list of photos. Add Property<String> fields named preview_url and
message to the component for this handler:
#![allow(unused)] fn main() { pub fn choose_photos(&mut self, _ctx: &NodeContext, event: Event<PhotoPickerChange>) { if event.status == PhotoPickerStatus::Cancelled { return; } if event.status == PhotoPickerStatus::Selected { if let Some(handle) = event.photos.first().and_then(|photo| photo.handle.as_ref()) { self.preview_url.set(handle.clone()); } self.message.set(event.message.clone().unwrap_or_else(|| { format!("Selected {} photo(s).", event.photos.len()) })); } else { self.message.set(event.message.clone().unwrap_or_else(|| { "No photo was added. Try another image or source.".into() })); } } }
Selected can include a warning: if some files exceed the configured size
limit, the accepted files can still be returned. SizeLimitExceeded reports
a selection in which the size limit prevented any accepted photos. Other
statuses are Cancelled, PermissionDenied, Unavailable, and Failed.
Keep the person's existing selection when a request is cancelled. Browser
dismissal does not always deliver a completion event in the current web
implementation, so do not depend on every close resetting an app-level
“picker open” flag.
Each photo includes a temporary identifier, MIME type, byte size, source kind, and optional file name, dimensions, preview handle, and copied bytes. Show a returned handle with NativeImage:
if self.preview_url != "" {
<NativeImage x=24px y=96px width=240px height=160px
url={self.preview_url} fit=ImageFit::Contain />
}
Handles are transient object URLs or temporary file URLs. Persist an app-owned copy if the image must survive beyond the current session. Selection does not upload a file; networking and storage belong to your Rust application logic.
include_bytes=true asks for copied data in photo.data. Web supplies it
when the read succeeds; the current Apple implementation returns metadata
and file handles without copied bytes in the event. Treat data as optional.
include_bytes=false avoids that web byte copy when a preview is enough.
The default max_bytes_per_photo is 25 MiB (26,214,400 bytes); a positive
limit is checked even when include_bytes is false. It is a per-photo
limit, so also consider how many selections your app retains.
Library, camera, and permissions
| Target | PhotoPickerSource::Library | PhotoPickerSource::Camera |
|---|---|---|
| Web | Browser file picker with image filtering and optional multiple selection | Requests the file input's camera-capture hint; the browser/device decides what UI is available |
| macOS | Image-file selection through NSOpenPanel | Returns Unavailable |
| iOS and iPadOS | System photo picker with optional multiple selection | System camera UI on a capable device, subject to permission |
On iOS and iPadOS, library selection uses the system's scoped photo picker.
The camera path needs a purpose string in the project's Cargo.toml:
[package.metadata.pax.ios.info_plist]
NSCameraUsageDescription = "Add a photo to your field notes."
iPadOS inherits the iOS metadata unless
[package.metadata.pax.ipados.info_plist] overrides it. Check permission
denial and unavailable hardware as well as a successful capture; a simulator
cannot stand in for every device capability.
Incrementing the picker's trigger property requests a programmatic open,
and the value is returned as request_id. On web, prefer direct activation
of the picker itself: browsers restrict opening file dialogs without a
user gesture, so a delayed property update is not a reliable substitute.
The canonical examples/src/photo-picker project shows library and camera
selection, metadata, size limits, and thumbnail previews. From the repository
root, run it with:
pax-cli run --path examples/src/photo-picker --target web
Current support
Button, Textbox (single-line and multiline), Checkbox, Slider, Dropdown, and RadioList have implementations for web, macOS, iOS, and iPadOS. Their shared Pax API does not make every platform detail identical:
- Web Slider applies
step; the current Apple sliders are continuous and do not apply it. If discrete values are essential to your interaction, this requires a platform-specific check before adopting the control. - Native styling and interaction states vary. For example, Button's
hover_coloris applied on web but not by the current Apple button views. - Multiline placeholders, photo bytes, and camera availability have the limits described above.
- Accessibility remains a partial foundation. Test required keyboard and assistive-technology behavior on each shipping target.
Read more
Continue to Animation and Motion for transitions and animated feedback. Compositing explains how native controls and rendered content share clipping and stacking; Scrolling covers longer forms and content regions.
For more detail, see the forms API, PhotoPicker API, event payloads, and image sources.
Animation and Motion
Motion can help someone follow a change: a new note arrives, a panel settles into place, or a list closes the space left by a removed item. Pax lets you describe that motion alongside the interface, with Rust controlling the state and actions that start it.
This chapter builds on Properties, Events, and Layout. Start with a property timeline, then give several tracks a shared playhead. Later sections cover enter/exit transitions and moving siblings in a Stacker.
For a hands-on preview, try Transition Grid: insert, remove, and reorder tiles while comparing their motion policies.
Timelines
A timeline is a sequence of property values at positions in time. Pax samples between those values as its playhead moves. Here, a small marker brightens and fades over a repeating 1.2-second cycle:
<Ellipse x=24px y=24px width=20px height=20px fill=rgb(56, 100, 78)
opacity=@timeline {
duration: 1.2s,
loop: true,
0%: 0.3, InOutQuad,
50%: 1.0, InOutQuad,
100%: 0.3,
}
/>
This can go in a component's template with use pax_kit::*; in its Rust file.
The inline timeline supplies the Ellipse's opacity. Each keyframe gives a
marker, a value, and optionally an easing curve for the segment that
leaves that keyframe. The first segment brightens from 0.3 to 1; the
second fades back to 0.3.
Markers, duration, and loops
Write an explicit duration so the timeline's scale is clear:
| Duration | Clock |
|---|---|
300ms or 0.3s | Elapsed milliseconds from the chassis's monotonic clock |
18 or 18f | Runtime frame count |
Use seconds or milliseconds when an interaction should take a particular amount of time across devices. A frame-based animation lasts for that many runtime ticks; its wall-clock duration depends on how those ticks arrive. Pax uses a nominal 60 frames per second when converting mixed authoring units, which is a conversion rule rather than a rendering-rate promise.
Markers can likewise be percentages, frame positions, or time values:
50%, 12, 12f, 150ms, or 0.15s. Percent markers make it easy to
change the overall duration while preserving the rhythm. For time-based
tracks, prefer percentages or matching time units; a bare 12 marker still
means frame 12.
Ordinary timelines loop by default. Use loop: false to clamp the playhead
to the range and keep the final value after the end. For a seamless loop,
give the first and last keyframes matching values, as above. Frame loops
include both frame zero and the final frame.
Without an explicit playhead, ordinary timelines sample the application's
$frames or $millis clock. Inserting an element later does not give that
ordinary timeline a fresh local clock. Use an enter transition
for motion that should start when the element appears, or supply a playhead
you control.
Easing
Easing changes how a value moves between two keyframes. A missing curve uses Linear.
| Curve | Character |
|---|---|
Linear | Constant progress through the segment |
Hold | Keep the first value until the segment ends |
InQuad | Begin slowly, then accelerate |
OutQuad | Begin quickly, then settle |
InOutQuad | Accelerate and then settle |
InBack, OutBack, InOutBack | Anticipation, overshoot, or both |
OutQuad is a useful starting point for a small arriving surface; InOutQuad can soften a movement between two resting positions. Back curves can exceed the interval between the endpoints, so leave room for the overshoot and avoid applying it indiscriminately to bounded values such as opacity.
Interpolation also depends on the property type. Numeric values, sizes,
rotations, and colors have interpolation behavior; arbitrary data does not
necessarily have a meaningful in-between value. Path command lists do not
currently interpolate their coordinates automatically. To change a shape,
animate numeric parameters and derive its geometry from them, as in the
mouse-animation example. For a reveal, animate a Path's draw_end; see
Drawing.
Share a playhead
A named timeline can coordinate several properties on elements selected by ID or class. A shared playhead lets a button, a slider, or application logic drive the whole sequence together.
Here is a complete small playback example. In src/lib.rs:
#![allow(unused)] fn main() { use pax_kit::*; #[pax] #[main] #[file("lib.pax")] pub struct Example { pub playhead: Property<f64>, } impl Example { pub fn replay(&mut self, _ctx: &NodeContext, _event: Event<ButtonClick>) { self.playhead.cancel_transitions(); self.playhead.set(0.0); self.playhead.ease_to( 100.0, Duration::Milliseconds(900.into()), EasingCurve::Linear, ); } pub fn pause(&mut self, _ctx: &NodeContext, _event: Event<ButtonClick>) { self.playhead.cancel_transitions(); } } }
In src/lib.pax:
<Button x=24px y=24px width=110px height=40px label="Replay"
@button_click=self.replay />
<Button x=148px y=24px width=110px height=40px label="Pause"
@button_click=self.pause />
<Slider x=24px y=84px width=280px height=32px
min=0.0 max=100.0 step=1.0 value=bind:self.playhead />
<Rectangle id=marker y=156px width=40px height=40px
corner_radius=8 fill=rgb(56, 100, 78) />
<Text id=caption x=24px y=224px width=280px height=40px
text="Ready for another page"
style={font: "Arial", font_size: 18px, fill: rgb(36, 54, 47)} />
@settings {
#marker { x: 24px }
}
@timeline reveal {
duration: 100,
playhead: {self.playhead},
loop: false,
#marker {
x: {
0%: {$base}, OutQuad,
100%: {$base + 200px},
},
rotate: {
0%: -12deg, OutQuad,
100%: 0deg,
},
},
#caption {
opacity: {
0%: 0,
40%: 0, OutQuad,
100%: 1,
},
},
}
Replay advances playhead from 0 to 100 over 900 milliseconds. The template
maps that one value to the marker's position and rotation and the caption's
opacity. Pause stops the Rust-side easing at its current value. Press Pause
before dragging the slider to scrub without an active animation writing
to the same property.
The timeline's duration: 100 establishes a 0–100 frame-position range.
Because playhead is supplied explicitly, it acts here as a convenient
sampling scale; the Rust easing determines how long playback takes.
A numeric playhead uses the track's clock units: frames for a frame-based
duration and milliseconds for a time-based duration, including one
written in seconds. A duration-valued playhead such as {(self.seconds)s}
carries its units through conversion. A numeric playhead is not automatically
normalized to 0–1.
Tracks inherit the enclosing timeline's duration, playhead, and loop setting unless overridden on the track. Multiple named timelines can bind to the same property when separate groups need the same progress. Naming the timeline organizes its tracks; playback comes from its clock or playhead, rather than an implicit Rust method named after it.
Relative values and property ownership
$base means the value underneath this setting or timeline layer. In the
playback example, #marker { x: 24px } supplies the base position, so the
track moves from 24px to 224px. Changing that base moves the whole animation
without repeating the layout value in every keyframe.
It does not mean the previous animation frame or the parent's property. Keyframe expressions remain reactive, so changing a dependency can change the sampled motion. See PAXEL's base values for the general model.
Keep one clear owner for each animated value. For ordinary selector
timelines, an inline assignment to that same property takes precedence:
adding x=24px directly to the marker above would hide the timeline's
x track. Put the base in a settings rule, as shown, or assign a property
timeline inline. Lifecycle transitions have their own overlay behavior;
do not generalize this ordinary-settings rule to @in and @out.
Also avoid animating a state property while a handler continually sets it, or trying to ease a derived value whose formula keeps recomputing. Animate an owned source property and let its dependents follow.
Imperative easing
Rust can animate a property directly with ease_to. It replaces that
property's pending transition queue and begins from its current eased value.
ease_to_later appends another segment. For a component with a
Property<f64> named strength, this handler rises, holds, and settles:
#![allow(unused)] fn main() { pub fn pulse(&mut self, _ctx: &NodeContext, _event: Event<ButtonClick>) { self.strength.ease_to( 1.0, Duration::Milliseconds(180.into()), EasingCurve::OutQuad, ); self.strength.ease_to_later( 1.0, Duration::Milliseconds(120.into()), EasingCurve::Linear, ); self.strength.ease_to_later( 0.2, Duration::Milliseconds(300.into()), EasingCurve::InOutQuad, ); } }
Bind strength to the visual property you want to animate. Triggering the
handler again replaces the unfinished pulse; it does not accumulate an
ever-longer queue.
Passing a plain number to either easing method means frames.
Duration::Frames(18.into()) is explicit frame timing;
Duration::Milliseconds(300.into()) and Duration::Seconds(0.3.into())
express elapsed time.
cancel_transitions() stops the active segment, clears the queue, and
leaves the current eased value in place. Call it before set(...) when an
immediate edit should take ownership. A plain set does not cancel a
previously queued animation.
Rust also accepts EasingCurve::Custom with a function. That is a Rust API;
arbitrary custom easing closures are not part of template timeline syntax.
See the animation API and
property API for the full method and
value-type contracts.
Enter and exit transitions
Use @in and @out for motion tied to an instance entering or leaving the
mounted tree. Their clocks start locally at the transition, and playback
is finite. Newly entering content can animate while old content is still
finishing its exit.
For a reusable note surface, declare this component in src/note_card.rs,
then add pub mod note_card; and use note_card::NoteCard; to src/lib.rs:
#![allow(unused)] fn main() { use pax_kit::*; #[pax] #[file("note_card.pax")] pub struct NoteCard { pub title: Property<String>, } }
In src/note_card.pax:
<Text x=16px y=16px width={100% - 32px} height={100% - 32px}
text={self.title}
style={font: "Arial", font_size: 18px, fill: rgb(36, 54, 47)} />
<Rectangle width=100% height=100% corner_radius=12
fill=rgb(237, 241, 226) />
@settings {
@in: enter
@out: exit
}
@timeline enter {
duration: 300ms,
self {
opacity: { 0%: 0, OutQuad, 100%: {$base}, },
y: { 0%: {$base + 16px}, OutQuad, 100%: {$base}, },
},
}
@timeline exit {
duration: 220ms,
self {
opacity: { 0%: {$base}, InQuad, 100%: 0, },
y: { 0%: {$base}, InQuad, 100%: {$base - 12px}, },
},
}
Here self targets the NoteCard instance itself, including its position
within the calling template. The named timelines can also use #id or
.class selectors for elements inside NoteCard's template.
Add a Property<bool> named show_note to a parent and toggle it from an
event handler. This conditional placement gives the card something to
enter and leave:
if self.show_note {
<NoteCard x=24px y=96px width=280px height=84px title="Moss and rain" />
}
Retention and interrupted motion
Removing the card from the conditional starts its exit. Pax retains the mounted instance until its exit finishes, then unmounts it. The application state has already changed; the retained visual gives the change time to read. Cleanup tied to unmount happens at actual unmount, not at the first request to leave.
The runtime enforces a five-second exit timeout to avoid retaining a node indefinitely. An exit that exceeds that limit can be truncated with a warning. Keep lifecycle exits short; use ordinary playback for a longer presentation.
Toggle the same conditional back before the exit completes and Pax can
rescue its still-mounted instance. The default interruption: Takeover
starts the destination transition from the currently sampled property
value. The destination's remaining keyframes, duration, and easing still
apply. This preserves value continuity; it does not promise the velocity
continuity of a physical spring.
Set interruption: Restart in the destination timeline when it should
start from its authored first value instead. The option belongs beside
duration in either a named or inline lifecycle timeline. It affects
direct enter/exit reversals on the same mounted instance, without
changing $base or ordinary timeline playback.
Conditional and route branches can reuse retained instances during a reversal. In repeated lists, stable keys make that identity explicit: reordering a retained item keeps its instance, and removing its key can start an exit. A newly allocated, unrelated instance has no earlier motion to take over. See Lists and identity and Routing.
Element-level transitions
For a local effect, put property tracks directly in an element's inline transition:
<Group x=24px y=24px width=280px height=84px
@in=@timeline {
duration: 300ms,
opacity: { 0%: 0, OutQuad, 100%: 1, },
}
@out=@timeline {
duration: 220ms,
opacity: { 0%: 1, InQuad, 100%: 0, },
}
>
<Text x=16px y=16px width=248px height=52px text="A brief note"
style={font: "Arial", font_size: 18px, fill: rgb(36, 54, 47)} />
<Rectangle width=100% height=100% corner_radius=12
fill=rgb(237, 241, 226) />
</Group>
Element bindings also accept names, such as @in=panel_enter. That
timeline belongs to the containing component: self addresses the element
that carries the binding, while selectors can reach other elements in the
same containing template. A reusable component's own settings-level
transition, like NoteCard's, targets its own template.
Container-owned motion
A child's enter/exit transition controls its visual motion. Stacker separately controls the space allocated to children and how that layout changes. There are two independent choices:
| Setting | Choices |
|---|---|
exit_mode | Flow keeps exiting children in normal layout until exit completes. Ghost holds their previous frames as overlays while active children lay out without them. |
reflow_transition.kind | Snap moves immediately to the new layout. Ease animates between layout frames. |
The defaults are Flow and Snap. To let surviving notes move into an
exiting note's space immediately, choose Ghost with Ease. For a parent
whose notes property contains items with stable id and title fields:
<Stacker x=24px y=24px width=280px height=320px gutter=12px
exit_mode=ContainerExitMode::Ghost
reflow_transition={
kind: ContainerReflowTransitionKind::Ease
frames: 18
curve: ContainerReflowCurve::OutQuad
name: ""
}>
for note in self.notes key note.id {
<NoteCard title={note.title} />
}
</Stacker>
Stacker reflow currently uses frames, not a duration field. Its
ContainerReflowCurve choices mirror the built-in easing names above.
ContainerReflowTransitionKind::Named is reserved and is not implemented
as a named-timeline lookup.
Try it: Transition Grid
Choose Remove to watch a tile leave. Switch from Flow to Ghost and remove another: compare when the remaining tiles begin moving into its space. Snap and Ease change how those remaining tiles reach their new positions.
Next, tap a tile to change its accent color and increment its counter, then choose
Reverse. The loop uses key cell.id, so each tile keeps its identity as
its position changes. A fifth tap replaces that tile with a new one at the
end of the list. Insert adds a fresh tile at the beginning.
The tile panel scrolls independently, keeping the policy controls in reach
as the list grows.
The source tabs show the container policies in lib.pax, the child's enter
and exit timelines in cell_button.pax, and the Rust handlers that update
the collection. Restart reloads the whole example; Open standalone
gives it a separate page.
Use keys and exercise rapid changes as well as settled states. A ghost may overlap its moving siblings while exiting, so consider clipping and visual order in the surrounding composition. Container reflow controls layout frames; animate a nested surface when you want a separate scale or flourish without making it responsible for the container's placement.
Paths, interaction, and motion choices
A playhead can come from more than a clock. A slider, pointer position, scroll position, or Rust simulation can supply the value that several tracks sample. Keep the mapping explicit and bounded: translate the input into the timeline's position range, then let the tracks own the visuals.
The marionette example uses shared and per-part playheads;
pax-logo exposes progress for coordinated vector motion; and
mouse-animation derives a moving shape from a parametric path.
timeline-playground explores longer sequences and mixed duration units.
These are deeper source references after the small examples here.
For handwriting or a drawn-line reveal, animate draw_start and
draw_end on Path or Handwriter. Drawing owns the
path geometry and reveal limits;
Scrolling owns scroll positions and viewports.
Animation supplies the changing value.
Choose motion that preserves the interface's meaning when paused or skipped. Keep long decorative sequences user-driven, and provide an application setting that can select a stable final state or a shorter transition when needed. Pax does not currently expose a unified reduced-motion preference in its public platform API; do not assume the examples automatically adapt to the operating system's preference.
The core timeline and property systems are shared by web, macOS, iOS, and iPadOS. The animated property still has its own renderer and native-control limits, and frame scheduling varies by target. Verify the finished interaction on the targets you ship rather than inferring visual parity from a successful web run.
Read more
Continue with Compositing and Effects for clipping, masking, and mixed native/rendered surfaces. Layout and the Stacker API cover ordinary container sizing. Events explains the handlers and lifecycle that drive state changes, while Accessibility and Native Controls covers keyboard and control behavior.
Compositing and Effects
A note card might combine a photograph, a vector border, editable text, and a native button. Compositing brings those pieces into one scene: what appears in front, what remains visible through a shape, and how transparency changes the result.
This chapter builds on Layout and Drawing. It covers the boundaries around content; Animation explains how to move those boundaries and change their properties over time.
Neon Opacity's Afterimage Observatory combines moving vector layers, transparency, and masked native controls. Adjust the BEAM slider in the Console to change the shared opacity (initially 75%), then try the text field or ENGAGE button while the scene moves. Watch how overlapping surfaces and their controls fade together. Open standalone gives the composition more room to explore.
The masks in this example use geometric coverage; the painted alpha mask section below describes a separate mode. Its motion is driven by frame-based Rust updates.
Choose the boundary
Earlier siblings in a Pax template appear in front of later siblings. Put the label before its surface and the surface before its shadow. Nesting gives a subtree a shared coordinate system, so moving or rotating its container carries its descendants with it. See element order and transforms for those foundations.
Choose a container based on what should happen at its edge:
| Container | Use it for |
|---|---|
Group | A shared transform and layout space, with content free to overflow |
Frame | A rectangular or rounded-rectangular clipping boundary |
Mask | A boundary supplied by another subtree's geometry or painted alpha |
These containers do not paint a background by themselves in an ordinary scene. Add a Rectangle or another drawing primitive when you want a surface. A rounded Rectangle changes its own silhouette; it does not clip text, images, or other siblings to that silhouette.
Clip with a Frame
A Frame clips its descendants to its bounds by default. corner_radius
rounds that boundary, using a number of pixels:
<Frame x=24px y=24px width=280px height=160px corner_radius=24>
<Text x=16px y=16px width=248px height=32px text="A view of the coast"
style={font: "Arial", font_size: 18px, fill: rgb(36, 54, 47)} />
<Ellipse x=196px y=80px width=140px height=140px
fill=rgb(72, 124, 112) />
<Rectangle width=100% height=100% fill=rgb(237, 241, 226) />
</Frame>
The ellipse extends beyond the Frame's right and bottom edges. Only the part inside the rounded boundary is visible. The Text participates in the same clipped composition even though it uses a native text surface.
The radius is clamped to fit the smaller dimension. It is a single radius for the clipping boundary; the four-corner list accepted by Rectangle is a different property contract. For ordinary grouping without a crop, use Group. Layout covers autosizing and choosing the Frame's dimensions.
Clipping changes visibility, not the child's layout size or application state. A clipped child remains mounted. Nested Frames further restrict the visible area; a child's transform does not free it from an ancestor's clip. Leave room inside that boundary for strokes and animated overshoot, or place the decoration outside the clipped subtree.
Image fitting and clipping are separate choices. An Image handles its own rectangular fit; a surrounding Frame can add rounded corners. See image fitting.
Shape content with a Mask
A Mask takes exactly two direct children:
- The content to show.
- The subtree that supplies the mask.
The alpha property selects how that second subtree is used:
| Mode | Source behavior | Content support |
|---|---|---|
alpha=false (default) | Geometric coverage gives a hard clipping boundary | Rendered and native content |
alpha=true | Painted alpha controls how much content remains visible at each point | GPU-rendered canvas content |
Use a Group when either side contains several elements. Here, the first Group contains a surface, native text, and a native button. The second child is an ellipse that selects the visible area:
<Mask x=24px y=24px width=280px height=180px>
<Group width=100% height=100%>
<Button x=70px y=84px width=140px height=36px label="Open note" />
<Text x=60px y=44px width=160px height=28px text="Moss and rain"
style={font: "Arial", font_size: 18px, fill: rgb(36, 54, 47)} />
<Rectangle width=100% height=100% fill=rgb(237, 241, 226) />
</Group>
<Ellipse x=20px y=10px width=240px height=160px fill=BLACK />
</Mask>
The ellipse itself is not painted as a black foreground object. Pax uses its transformed coverage path to clip the first child's descendants, including native surfaces. Black is simply a convenient fill for the source shape.
Both sides use the Mask's coordinate space. Percentage dimensions resolve against that space; positions, rotations, and nested transforms carry through to the resulting coverage. Bind a source shape's position or size to a property to move the visible window, or animate those values with a timeline. Moving the source changes the window without moving the content underneath it.
Use a path or a subtree
For a custom silhouette, replace the second child with a filled, closed Path. This one cuts the corners off a rectangular label:
<Path width=100% height=100% fill=BLACK
elements={[
PathElement::Point(12%, 0%),
PathElement::Line, PathElement::Point(88%, 0%),
PathElement::Line, PathElement::Point(100%, 20%),
PathElement::Line, PathElement::Point(100%, 80%),
PathElement::Line, PathElement::Point(88%, 100%),
PathElement::Line, PathElement::Point(12%, 100%),
PathElement::Line, PathElement::Point(0%, 80%),
PathElement::Line, PathElement::Point(0%, 20%),
PathElement::Close
]} />
In the default geometry mode, Mask collects coverage paths from descendants of its second child. A source Group can therefore contain several shapes. Keep the source tree small and purposeful: only geometry with a coverage-path implementation contributes. A Group by itself contributes no shape, and native text does not turn into letter-shaped clipping geometry.
The source paths are combined for clipping; this is not a general boolean geometry editor. For holes, overlapping contours, or self-intersections, verify the result on the target renderer instead of assuming a particular union or subtraction rule. Drawing owns path commands and SVG conversion.
Geometry and transparency
With alpha=false, the mask uses geometric coverage rather than painted
transparency. An Image contributes its rectangular bounds, not the silhouette
of its transparent pixels. Use alpha=true for soft coverage authored with
vector fills, strokes, and gradients, as shown below.
Use explicitly filled shapes for predictable source coverage. Shape APIs decide which geometry contributes: for example, a Path with no visible fill or stroke may have no coverage at all. If the source produces no coverage path, geometry mode applies no clip; it does not hide everything. Use conditional content when the intended state is “show nothing.”
A nested Mask can further constrain already-masked content. In geometry mode, the source's paint appearance does not determine coverage. In either mode, source-side Frame and Mask clips are not applied to the source paints; keep the source tree focused on the shapes that define the reveal.
Painted alpha masks
Set alpha=true to reveal content according to the source's painted alpha.
Opaque paint reveals fully, transparent paint hides, and partial alpha gives
partial visibility. The source's RGB color is irrelevant: opaque black and
opaque white both reveal fully. This is alpha masking, not luminance masking.
Here a gradient reveals a green surface through the middle and fades it away at both ends:
<Mask x=24px y=24px width=280px height=120px alpha=true>
<Rectangle width=100% height=100% fill=rgb(56, 100, 78) />
<Rectangle width=100% height=100%
fill=@gradient {
linear: { start: [0%, 50%] end: [100%, 50%] }
0%: rgba(0, 0, 0, 0)
25%: rgba(0, 0, 0, 255)
75%: rgba(0, 0, 0, 255)
100%: rgba(0, 0, 0, 0)
} />
</Mask>
Rectangle, Ellipse, and Path fills and strokes can supply alpha, including solid alpha and linear or radial gradient stops. Source transforms and opacity apply too. Use up to eight ordered gradient stops. A Group or repeated subtree can combine supported source shapes; overlapping paints combine with source-over alpha, so two half-opacity shapes give 75% coverage where they overlap.
Add feather=2.0 to the Mask to soften its painted coverage. feather is the
Gaussian standard deviation in logical pixels, rather than a percentage or a
Pax length literal. It defaults to zero; negative values are treated as zero.
It affects alpha masks only and does not blur the revealed content itself.
Living Quilt uses alpha=true feather=2.0 for its moving color waves. See the
live Living Quilt and its source for the
complete two-child composition: a colored quilt followed by a Group of repeated
animated paths that supplies the coverage.
Nested alpha masks on the content multiply their coverage, while ordinary geometric clips further restrict the result. An empty or fully transparent alpha source hides all content. Alpha masking modulates individual canvas draws; it does not flatten the content into an isolated group before applying transparency.
The current alpha path requires the GPU renderer. Native text and controls are not supported as content inside an alpha mask, and Piet does not support alpha masking. Images, text, native elements, and source-side clipping are not supported alpha sources. For a mixed native/rendered composition, keep the default geometry mask; for a soft visual reveal, use supported vector source paint and keep interactive hit targets separate. Alpha coverage does not change hit testing.
Opacity through a subtree
opacity accepts a normalized value such as 0.5, or a percentage such as
50%. Each node inherits its render parent's opacity and multiplies it by
its own value. Paint alpha is another multiplier:
<Group x=24px y=24px width=280px height=120px opacity=50%>
<Rectangle x=0px y=0px width=160px height=100px
fill=rgb(56, 100, 78) />
<Rectangle x=100px y=20px width=160px height=100px
fill=rgb(56, 100, 78) opacity=50% />
</Group>
The first Rectangle paints at 50% opacity. The second inherits that 50%
and multiplies it by another 50%, so it paints at 25%. Giving the second
Rectangle opacity=1 would preserve the inherited 50%; it would not undo
the parent's attenuation. Drawing explains
paint alpha, including rgba units.
This is per-descendant opacity. Group, Frame, and Mask do not provide a general “render the subtree to one image, then fade that image” isolation operation. Overlapping translucent descendants can build up opacity where they overlap. Adding another Group does not remove that buildup.
Choose the property that matches the visual intent. To soften a card's background while keeping its label crisp, lower the background fill's alpha. To fade the card's individual pieces together, change a shared ancestor's opacity and inspect their overlaps. Two overlapping copies at 50% each do not produce one fully opaque copy; avoid complementary crossfades when a silhouette must stay solid throughout the handoff.
Opacity does not unmount a component, disable a control, or manage keyboard focus. Keep those state and interaction decisions explicit. Likewise, a visual mask is not an accessibility description or a promise of pixel-exact hit testing for every control. See Accessibility and Native Controls.
Native compositing
Pax draws vector primitives and Image content through its renderer. Text, NativeImage, and form controls use native surfaces supplied by the browser or operating system. Those surfaces share the Pax scene's transforms and ordering, even though they are not all painted into the same canvas.
To make rendered content appear above a native control, Pax computes its coverage and masks the corresponding portion of the native surface. This is often called punch-through in the implementation. You normally express the desired order through the template:
<Group x=24px y=24px width=280px height=120px>
<Ellipse x=180px y=0px width=80px height=80px fill=rgb(207, 120, 79) />
<Button x=24px y=24px width=220px height=44px label="Partly covered" />
<Rectangle width=100% height=100% fill=rgb(237, 241, 226) />
</Group>
The ellipse is earlier in the template, so its overlapping area appears in front of the Button. The later Rectangle stays behind both. Moving the ellipse updates that relationship; wrapping the composition in a Mask adds another visible boundary.
Coverage has limits
Native punch-through uses coverage geometry and an opacity estimate; it does not sample every final rendered pixel. This matters for subtle cross-surface blends:
- A gradient with changing alpha has one estimated coverage opacity for the native mask, rather than a separate alpha value at each pixel.
- A rendered Image's coverage is rectangular, including transparent pixels in the source image.
- A partially revealed Path can retain conservative coverage for its full stroke so the runtime does not rebuild native occlusion geometry on every animation sample.
These cases can differ from placing both pieces in a single paint surface.
Keep important text and controls clear of such overlaps, and verify an
intentional cross-surface effect on the target where it will ship. The
occlusion and neon-opacity examples are useful places to explore the
current behavior.
Scroller islands and modal underlays
A native Scroller can host its own rendered-content surface, often called an island. Its scrolling and clipping belong to that host. A translucent Rectangle on the root canvas therefore cannot be assumed to tint every native control and scroller island as if the whole interface were one image.
For a modal background that must both dim the scene and absorb pointer
input, use an EventBlocker with a translucent solid background. Place the
panel before it and the underlying content after it:
<Group width=100% height=100%>
<Group x=50% y=50% width=280px height=120px>
<Text x=20px y=20px width=240px height=32px text="A moment to pause"
style={font: "Arial", font_size: 18px, fill: rgb(36, 54, 47)} />
<Rectangle width=100% height=100% corner_radius=16
fill=rgb(237, 241, 226) />
</Group>
<EventBlocker width=100% height=100% background=rgba(0, 0, 0, 128) />
<Button x=24px y=24px width=160px height=44px label="Underlying action" />
<Rectangle width=100% height=100% fill=rgb(248, 244, 234) />
</Group>
This demonstrates the surface ordering, not a complete modal component. In an application, mount the panel and blocker conditionally and provide dismissal and focus behavior. EventBlocker's background is transparent by default. It is a native surface that absorbs pointer input; it does not establish a keyboard focus trap. Routing covers route-driven panels and their lifecycle.
Platform-specific effects
Apple Liquid Glass
LiquidGlass provides an inherited effect scope for supported Apple native surfaces. A Group inside it can supply a rounded glass surface, while supported controls receive the native treatment:
<LiquidGlass x=24px y=24px width=280px height=160px
spacing=12px variant="regular">
<Group width=100% height=100% corner_radius=20>
<Text x=20px y=20px width=240px height=32px text="Field notes"
style={font: "Arial", font_size: 18px, fill: rgb(36, 54, 47)} />
<Button x=20px y=76px width=240px height=44px label="Continue" />
</Group>
</LiquidGlass>
variant supports "regular" and "clear". tint supplies an optional
color; spacing controls grouping distance. interactive=true requests
the interactive treatment where supported. A nested
<LiquidGlass enabled=false> opts its descendants out of the inherited scope.
The Apple implementation uses native glass on macOS 26 and iOS/iPadOS 26 or later, with native visual-effect fallbacks on earlier supported OS versions. The details differ by platform; for example, the current UIKit implementation applies the interactive flag, while the macOS implementation does not provide the same response. Web retains ordinary controls without the Apple glass treatment. Give that fallback an intentional, readable composition rather than depending on glass for contrast.
Use examples/src/liquid-glass to explore the native effect and nested
opt-outs on Apple targets. A successful web run checks the fallback, not
the Apple appearance. The LiquidGlass API
lists the scope's properties.
Lighting and other effects
Light-reactive vector materials and LightFrame are taught in Drawing. Their GPU lighting changes the drawn material; it does not automatically blur or refract all the native content behind it. Piet renders those materials unlit.
In Materials, scroll through the collection of imagined elements. Scrolling moves a light across surfaces with matte, glossy, metallic, emissive, and custom materials. Compare how their highlights respond, then open the Rust source to see the material parameters and scroll-driven light position. The lighting requires the WGPU renderer; Piet shows the unlit fills and strokes.
A general-purpose Gaussian blur wrapper, configurable blend modes, and portable backdrop materials are not part of the current public toolkit. You can still build depth with offset shapes, gradients, transparency, and motion. Keep authored shadows and glows distinct from a sampled backdrop effect when explaining what an example demonstrates.
Check the finished composition
Start with the smallest composition that shows the intended relationship. Check it at rest, after resizing, during scrolling, and at intermediate animation values. Include a native control in the test if the finished interface mixes native and rendered content; an all-vector mockup does not exercise that boundary.
When something disappears, inspect source order and ancestor clips first, then Mask child order and source coverage, followed by inherited opacity. If the issue appears only while crossing a native surface or Scroller, compare the surface arrangement as well as the element's local geometry.
From the repository root, run a canonical testbed:
pax-cli run --path examples/src/occlusion --target web
neon-opacity adds animated nested transparency and mixed controls.
Verify the target and renderer you intend to ship: web, macOS, iOS, and
iPadOS share the authoring model, but their native surfaces and effect
implementations have the qualifications described here.
Read more
Continue with Scrolling and Viewports for content coordinates and scroll-driven composition. Revisit Layout for transforms and bounds, Drawing for paint and geometry, and Animation for changing those values over time. The Frame, Mask, and EventBlocker references list their public APIs.
Scrolling and Viewports
A list of notes, a horizontal shelf, and an illustrated story all need a way
to show content that extends beyond the available space. Scroller gives
that content a viewport, clips it at the edges, and connects it to the
platform's scrolling behavior on web, macOS, iOS, and iPadOS.
The scroll position is also a property. You can use it to return to the top, show reading progress, or move through an animation as the reader explores.
This chapter builds on Layout and property handles and bindings. For Rust handlers, see Event Handling.
Viewport, content, and position
A Scroller has three related sets of dimensions:
| Property | Meaning |
|---|---|
width, height | The visible viewport, placed by ordinary Pax layout |
scroll_width, scroll_height | The extent of the scrollable content pane |
scroll_pos_x, scroll_pos_y | The offset into that pane, as numeric pixel values |
Here is a 240-pixel-tall viewport onto 720 pixels of notes. Paste this into
src/lib.pax in a project whose Rust main component imports pax_kit::*:
<Scroller x=24px y=24px width={100% - 48px} height=240px
scroll_width=100% scroll_height=720px corner_radius=16>
<Group width=100% height=720px>
for i in 0..4 {
<Group y={(i * 180)px} width=100% height=180px>
<Text x=20px y=20px width={100% - 40px} height=40px
text={"Field note " + (i + 1)}
style={font: "Arial", font_size: 24px, fill: rgb(36, 54, 47)} />
<Rectangle x=8px y=8px width={100% - 16px} height=164px
corner_radius=12 fill=rgb(225, 234, 220) />
</Group>
}
</Group>
</Scroller>
Increasing scroll_pos_y moves the visible window farther down the content;
the notes move upward on screen. At rest, the vertical range here is
0 through 720 - 240 = 480 pixels. If the content fits inside the
viewport, there is no travel on that axis. Platform overscroll and bounce
can temporarily present the edges differently.
scroll_height=300% would also describe 720 pixels for this viewport:
percentages on the scroll extent are relative to the Scroller's own viewport.
The extent establishes how far you can scroll; it does not stretch the child
tree to that size. A direct child at height=100% still receives the
viewport-height layout frame. The explicit 720-pixel Group above gives its
descendants a content-sized coordinate space.
Scroller does not paint a background. Place painted content inside it, or a
background sibling behind it, depending on which should move. Earlier Pax
siblings appear in front; that is why each note's Text precedes its Rectangle.
corner_radius is a numeric pixel radius for the viewport clip.
Autosized Scrollers
For a document that grows as you add content, let Scroller measure its pane:
<Scroller x=24px y=24px width={100% - 48px} height=240px
scroll_width=100% autosize=true corner_radius=16>
<Stacker width=100% autosize=true gutter=12px>
for i in 0..5 {
<Text width=100% height=80px text={"Measured note " + (i + 1)}
style={font: "Arial", font_size: 24px, fill: rgb(36, 54, 47)} />
}
</Stacker>
</Scroller>
The stack measures five 80-pixel children and four 12-pixel gutters. Scroller uses that 448-pixel content height while its viewport remains 240 pixels tall. Changing the children or their measured sizes updates the scrollable extent.
autosize=true on Scroller manages the vertical content extent by default.
Horizontal size continues to use scroll_width; autosize_x=true opts into
horizontal measurement. autosize_y=false turns off vertical measurement even
when autosize=true.
On a measured axis, Scroller uses its resolved content measurement in preference
to scroll_height or scroll_width; the explicit value is a fallback if that
measurement cannot be resolved. The public size property remains the input,
so reading self.some_scroll_height does not automatically give you the
measured result. This differs from asking a container to measure its own
outer dimensions.
Give measured content a useful starting point: concrete row heights, text
measurement, or an autosized stack. Avoid making an expanding document depend
only on height=100%. Font loading and native measurements can change the
settled extent. Layout's autosize section
explains the shared measurement rules.
Read and set the scroll position
Use bind: to share the Scroller's position with a component property. User
scrolling updates that property; setting it from Rust requests a new position.
This also gives buttons and visual indicators one place to read the state.
In src/lib.rs:
#![allow(unused)] fn main() { use pax_kit::*; #[pax] #[main] #[file("lib.pax")] pub struct Example { pub scroll_y: Property<f64>, } impl Example { pub fn back_to_top(&mut self, _ctx: &NodeContext, _event: Event<ButtonClick>) { self.scroll_y.set(0.0); } pub fn last_note(&mut self, _ctx: &NodeContext, _event: Event<ButtonClick>) { self.scroll_y.set(480.0); } } }
In src/lib.pax:
<Button x=24px y=24px width=120px height=36px label="Back to top"
@button_click=self.back_to_top />
<Button x=156px y=24px width=120px height=36px label="Last note"
@button_click=self.last_note />
<Text x=24px y=72px width=280px height=28px
text={"Offset: " + self.scroll_y}
style={font: "Arial", font_size: 18px, fill: rgb(36, 54, 47)} />
<Scroller x=24px y=120px width={100% - 48px} height=240px
scroll_width=100% scroll_height=720px scroll_pos_y=bind:self.scroll_y>
<Group width=100% height=720px>
for i in 0..4 {
<Text x=16px y={(i * 180 + 16)px} width={100% - 32px} height=80px
text={"Field note " + (i + 1)}
style={font: "Arial", font_size: 24px, fill: rgb(36, 54, 47)} />
}
<Rectangle width=100% height=100% fill=rgb(225, 234, 220) />
</Group>
</Scroller>
Use plain numbers for these offsets: 480.0, rather than 480px. Their Rust
type is f64; the extent properties use Size and accept px or %.
The example's 480-pixel destination comes from its known dimensions. For a responsive document, derive destinations from current layout and content, and keep them within the available travel. Content shrinkage and viewport resizing can constrain what the native host can show. Avoid repeatedly writing a saved position while the person is actively scrolling; native gesture updates and application writes would compete for the same state.
Position versus scroll events
@scroll sends an Event<Scroll> with delta_x and delta_y. It is a shared
delta stream used by wheel/touch input and native position notifications.
It is useful when an action needs movement information. Use the bound
scroll_pos_y for reading progress or a return position, rather than building
a second position by accumulating event deltas.
Native position notifications report movement that has already happened.
They are not a cancellable, cross-platform “before scrolling” hook. Likewise,
@wheel describes wheel input and does not cover every way someone can scroll.
See Event Handling for event binding and propagation.
Horizontal regions and snapping
For a horizontal shelf, make scroll_width larger than width and keep the
vertical extent at 100%. The same geometry works on both axes.
Snap positions add landing points. This strip contains three viewport-width panels and snaps at their starts:
<Scroller x=24px y=24px width={100% - 48px} height=180px
scroll_width=300% scroll_height=100%
snap_positions_x=[0px, 100%, 200%] corner_radius=16>
for i in 0..3 {
<Group x={(i * 100)%} anchor_x=0% width=100% height=100%>
<Text x=20px y=20px width={100% - 40px} height=48px
text={"Panel " + (i + 1)}
style={font: "Arial", font_size: 24px, fill: rgb(36, 54, 47)} />
<Rectangle x=6px width={100% - 12px} height=100%
corner_radius=16 fill=rgb(225, 234, 220) />
</Group>
}
</Scroller>
Each panel uses anchor_x=0% so its percentage position locates its left edge.
Snap percentages resolve against the viewport on the corresponding axis.
snap_positions_y provides the vertical equivalent. Leave the lists empty
for ordinary continuous scrolling. Web uses native CSS scroll snapping;
Apple targets choose native scroll endpoints from the supplied positions.
Gesture momentum and settling can differ between platforms.
Carousel pages
Carousel packages page layout, content extents, and snapping. Each supplied
child becomes a page:
<Carousel x=24px y=24px width={100% - 48px} height=220px
axis=CarouselAxis::Horizontal page_size=100% show_dots=true>
<Rectangle width=100% height=100% fill=rgb(225, 234, 220) />
<Rectangle width=100% height=100% fill=rgb(242, 218, 183) />
<Rectangle width=100% height=100% fill=rgb(205, 224, 236) />
</Carousel>
Horizontal paging and page_size=100% are the defaults. Choose
CarouselAxis::Vertical for vertical pages, and bind scroll_pos_x or
scroll_pos_y when the application needs the position. page_size controls
each page's extent along the scrolling axis; its percentage is relative to
the Carousel viewport. With only one child, that page fills the viewport.
Dots are optional position indicators, hidden when there is only one page. They are not clickable navigation controls. Provide explicit previous/next buttons when your interface needs them, using the bound scroll position.
Scroll-driven motion
Scrolling can reveal a drawing, turn a diagram, or carry a caption through a sequence. Start with a normalized position:
progress = scroll position / (content extent - viewport extent)
Clamp the result to 0..1 and handle a zero-length scroll range. Then map
progress to the timeline's playhead range. The dimensions in the state
example give a travel of 480 pixels, so this addition to its lib.pax makes
a reading-progress bar. Add the Group before the Scroller and place the
timeline at the end of the file:
<Group x=24px y=104px width={100% - 48px} height=6px>
<Rectangle id=reading_progress height=100% fill=rgb(56, 100, 78) />
<Rectangle width=100% height=100% fill=rgb(210, 218, 206) />
</Group>
@timeline reading {
duration: 100,
playhead: {Math::min(1, Math::max(0, self.scroll_y / 480)) * 100},
loop: false,
#reading_progress {
width: {
0%: 0%, Linear,
100%: 100%,
},
},
}
Here duration: 100 defines the timeline's sampling range. There is no
running clock: scrolling backward samples earlier positions, and resting
leaves the bar still. A responsive version needs current viewport and content
measurements in place of the fixed 480 denominator. Keep the Scroller's
geometry independent of the decorative animation so the progress mapping
does not change its own scroll range.
The same playhead can drive several tracks. The canonical scroll-garden
example binds a vertical Carousel to a playhead for its articulated scenes.
Animation and Motion explains track
ownership, easing, and playback; Drawing
owns Path and Handwriter reveals.
Nested viewports and native content
A horizontal shelf can live inside a vertical document. Give each Scroller a
bounded viewport and its own content extent. For example, place the horizontal
strip above inside a taller content Group in the outer Scroller. Its width
then follows that group's width, while its 180-pixel height remains a visible
window onto the strip.
Use nesting when the regions have distinct jobs. Same-axis nesting makes gesture ownership harder to anticipate; test what happens at each edge with the target's trackpad, mouse, and touch input. Pax relies on the platform scroll container for native scrolling, momentum, and gesture arbitration.
Text, form controls, vectors, and images can share the scrolling content tree. The native host moves the content, while Pax maintains the rendering, clipping, and input-coordinate relationships. A web root Scroller that fits the viewport and scrolls only vertically can delegate to page scrolling; you still use Scroller properties rather than browser DOM operations.
For a toolbar that stays in place, keep it outside the Scroller as a sibling,
usually reserving space for it in the surrounding layout. A child at y=0px
belongs to the scrolling content and moves with it. layout_role=LayoutRole::Breakout
removes a child from the relevant layout measurement/placement rules; it
does not let that child escape the Scroller's clipping or rendering tree.
Use NodeContext::local_point for custom pointer interaction inside moving
content. On iOS and iPadOS, scroll recognition can suppress a tap while
the child still receives its lower-level touch sequence. The
touches-inside-a-Scroller section
explains how to clear transient feedback. Check native controls and nested
gestures together on the targets you ship.
Scroller-owned rendering surfaces also matter for overlays. A translucent root Rectangle is not a universal dimmer over every native scroll region. Compositing owns the cross-surface explanation and modal-underlay pattern.
Large collections and practical checks
Pax uses viewport-aware drawing and tiled surfaces to limit rendering work
for scrollable content. That does not make for a virtualized list: repeated
children still participate in tree expansion, properties, and lifecycle.
Offscreen components may still cost startup time and perform application work.
Start with realistic collection sizes and measure first paint as well as scrolling. For a large data set, consider application-level paging or loading bounded batches. Stable keys preserve item identity during changes; they do not defer offscreen initialization. See Components for collection ownership.
Exercise the finished view with its real text, images, and controls:
- Resize it and change the content count, including an empty or short list.
- Reach the first and last items, then use a position button and scroll again.
- Try nested regions, native-control interaction, and rounded edges.
- Test fast movement on the actual browser/backend and Apple devices you support. Startup cost and tile presentation remain workload- and target-dependent; a desktop web check cannot establish every target's behavior.
From the repository root, these examples offer larger inspection surfaces:
pax-cli run --path examples/src/rounded-scroller-tiles --target web
pax-cli run --path examples/src/scroll-matrix --target web
pax-cli run --path examples/src/scroll-garden --target web
Run one at a time. rounded-scroller-tiles focuses on rounded viewports and
mixed content; scroll-matrix exercises nesting, transforms, and controls;
scroll-garden explores scroll-driven scenes. Their source lives under
examples/src/ and remains the place to follow the complete applications.
Read more
- Layout and Responsiveness: parent frames, measurement, and responsive structure.
- Properties: bindings and derived state.
- Event Handling: coordinate conversion and gestures.
- Animation and Motion: playheads, tracks, and motion choices.
- Compositing and Effects: clips, masks, and native surfaces.
- Scroller API and Carousel API: property reference.
How Pax Runs
A Pax application has a reactive scene behind the interface you see. Its properties carry values, its component instances organize the scene, and its platform integration brings together drawn content and native controls. Understanding those pieces helps you explain an update, investigate a slow interaction, and choose what to measure before shipping.
The same core model runs on web, macOS, iOS, and iPadOS. This chapter follows it from source to the screen. It builds on Properties, Events, and Layout; What is Pax? introduces the authoring model.
Compiler, runtime, and chassis
Three parts work together to run your application:
| Part | Responsibility |
|---|---|
| Compiler | Analyzes the application and its templates, prepares the declarative program, and generates the connections to Rust component types and handlers |
| Runtime | Instantiates the scene, maintains reactive relationships, handles events, computes layout, and coordinates rendering and native-element updates |
| Chassis | Connects the runtime to a target's window or browser, graphics surfaces, input, native elements, and platform services |
The CLI coordinates the build. Rust application logic is compiled for the selected target: WebAssembly for the browser, or native code for the Apple targets. Pax's generated program data describes templates, bindings, events, and timelines so the runtime can instantiate them.
You may encounter the word cartridge in build output or engine code. It names the application-specific part that connects your program to the shared runtime. The CLI generates this connection; you do not maintain a cartridge file by hand. Build for each destination target—there is no single executable cartridge file that you can interchange between a browser and an iOS app.
At a high level:
.pax templates + Rust application + asset references
│
compiler / build
│
target application + packaged assets
│
runtime mounts the main component
│
reactive scene + platform chassis
│
drawn surfaces + native elements
Local assets are included in the target's output as appropriate; external URLs may still be loaded at runtime. An image or font referenced by the program is not necessarily embedded inside its Wasm or native executable.
From templates to running instances
A template is a definition. The runtime creates instances from it: component state, element properties, and nodes with resolved layout and parent-child relationships. This running structure is called the expanded tree in inspection and runtime code.
One repeated element in a template can produce many nodes in that tree. A conditional can mount or remove a branch as its condition changes. Slots connect supplied content to the place where a component presents it. These are useful distinctions when the visible scene contains more—or fewer—nodes than you expect from counting tags in a file.
The tree and property graph answer different questions. The tree says where an instance belongs; the graph records which values depend on which inputs. A child's width can depend on its parent's bounds, while its text depends on application state shared from elsewhere. Those dependencies do not have to follow the same shape as the tree.
State also has a lifetime. Removing and recreating a component can recreate
its local state. Keep state that must outlive a particular view in an
appropriate owner, and persist it explicitly when it must survive an
application restart. A Property is a reactive value, not persistent storage.
See Components and Composition for ownership and
shared state.
From a property change to the screen
Consider a handler that increments a counter used in a Text element. The handler sets the source property. Pax marks dependent values dirty: their cached values may no longer be current. When a dependent computed value is needed, the graph evaluates it using the current inputs.
The runtime also drains registered reactive effects that need to push an update into the scene. Multiple writes can settle into one downstream evaluation when that value has not been read or its effect drained between the writes. This does not make a sequence of arbitrary property writes an atomic transaction.
The consequences depend on what changed. New text can require a native text
update and measurement. A different width can change descendant layout. A
new fill can mark drawn content for rendering. Changing an if condition
can alter the mounted tree and its event registrations.
Pax tracks dirty nodes and layers so unchanged drawing can be reused or skipped. The GPU path retains rendering data, and the runtime can avoid a canvas rendering pass when there is no canvas work pending. Native elements receive their own updates through the chassis.
The amount of work is therefore related to the dependencies and surfaces affected by a change. A single property write can still have a broad effect: resizing a container, removing an overlapping element, or changing a clip can require work beyond the element you edited. Dirty tracking does not guarantee that every update redraws only the smallest possible pixel region.
Properties covers computed values, subscriptions, and avoiding unnecessary writes. Layout explains the bounds and transforms that these updates can affect.
Work that continues between interactions
The runtime advances clocks and invokes registered lifecycle handlers such
as @tick and @pre_render. A running transition or timeline produces new
values as time advances. These are legitimate sources of work even when
the person using the application is not touching anything.
The current web chassis continues to schedule animation-frame callbacks while running. Dirty tracking reduces work inside those callbacks; it does not establish zero CPU usage at rest or suspend the frame loop altogether. An otherwise static page can still have clock, lifecycle, or platform work.
Keep expensive data processing and repeated no-op state writes out of per-frame handlers when they are not needed. Use a reactive expression for a value derived from other properties, and use the motion APIs for the transitions they describe. Neither choice makes computation free, but it gives the runtime explicit dependencies to work with. See Animation and Motion.
One scene, multiple surfaces
Drawn geometry and native elements share Pax layout, transforms, and ordering. Their presentation is divided across graphics surfaces and platform-owned elements. The compositor coordinates those pieces, including the places where clipping, scrolling, and overlap cross surface boundaries.
For example, a Rectangle can be drawn into a graphics layer while a Textbox is a browser control or Apple-native view. The runtime sends the native element its required updates; user input returns through the chassis to the runtime and application handlers. The platform still handles native concerns such as editing interactions and input-method behavior.
This division preserves useful native behavior, while making backend and element type important when choosing an effect. An effect supported on drawn geometry does not automatically support a native control or every form of text and image rendering. The detailed boundaries belong in Compositing and Effects and Accessibility and Native Controls.
Rendering backends
On the web, Pax currently selects its WebGPU renderer where browser support and policy allow. It uses a Piet/CPU browser renderer when WebGPU is unavailable, when the current iOS Safari detection policy selects that path, or when built explicitly with the Piet backend. Piet draws through browser canvases; native browser elements remain native on either path.
Backend selection is not a guarantee of recovery from every graphics-device initialization failure. When diagnosing a browser-specific problem, check the selected renderer and startup errors rather than assuming that another path was selected successfully.
The Apple-native chassis use Pax's GPU rendering integration alongside native views. Running a web build in Safari on an iPhone is a different target path from running a native iOS build on the same phone. Test the destination your users will actually use.
Graphics effects, surface limits, and rendering costs can differ between backends. There is no universal frame-rate guarantee for a Pax application. Scene complexity, assets, device capabilities, and the chosen renderer all contribute to the result.
Viewports and large collections
Clipping and culling can reduce the drawing submitted for content outside a viewport. Scroller surfaces can also be tiled to limit the backing surfaces needed for a large scrollable area. These are rendering optimizations.
The repeated component instances, properties, and application data still
have their own cost. Putting a large for loop inside a Scroller does not
automatically turn it into a virtualized list. Check node count and state
work as well as what is visible. Scrolling and Viewports
covers content measurement and practical collection limits.
Debug and release
Debug builds support the development loop, including richer program metadata and the configured hot-reload lanes. Release builds optimize the compiled application and bake its declarative program into the artifact. The build paths differ internally, so a successful debug run is only one part of checking an application intended for release.
Release applications disable the live .pax and application-logic reload
lanes. Hot reload is a development capability; keeping it available does not
require shipping it in the release application. See
Developer Workflow for the current reload choices.
From your project directory, build the web release with:
pax-cli build --target web --release
Test the resulting application in addition to the debug session. Use the release output when measuring shipping performance or download size. The CLI reports bundle statistics, but those figures do not describe every asset or network request your application may load. Include images, fonts, scripts, and other resources when assessing the complete experience.
For investigating Wasm size, the CLI also offers build --profiling on web,
which produces an optimized build with Wasm names retained for size analysis.
This is a size-investigation mode, not a runtime frame profiler. Measure the
ordinary release build for the shipping comparison.
Inspect an update in a working example
The repository's examples/src/increment app has a useful pair of behaviors:
clicking its rounded rectangle changes the count and starts a rotation, while
a @pre_render handler continuously changes the rectangle's color.
From a Pax repository checkout, start it with:
pax-cli run --path examples/src/increment --target web
With that session running, use another terminal in the same checkout:
pax-cli dev inspect tree --path examples/src/increment --max-depth 3
pax-cli dev logs --path examples/src/increment --limit 50
The tree shows the running instances behind the template. The log command
reads messages captured by the development session; a quiet session can
return no entries. To include renderer selection, add ?pax_log=info to the
local application's URL and reload it, then run the log command again.
The default debug logging level includes warnings and errors. You can also
read these diagnostics in the browser console. These commands help establish
what is running; they do not measure the time spent inside each part of a frame.
Open src/lib.pax and src/lib.rs inside the example and follow a click:
- The Group binds
@clicktoincrement. - The handler updates
num_clicks, which feeds the Text's expression. - It starts an eased transition on
current_rotation, which feeds the Group'srotateproperty. - The rotation settles, but
handle_pre_renderkeeps updatingticks. The Rectangle's fill depends on that value, so its color keeps changing.
This app intentionally remains animated. To understand activity after an interaction in your own application, follow the same trail: event handler, property dependencies, active motion, and per-frame handlers.
Measure the question you have
Separate startup, interaction, continuous motion, and resting-state work. They can have different causes and require different checks:
- For a slow initial load, inspect resource requests and decoding, executable size, and scene initialization. Compare a cold load with a warm one.
- For an expensive interaction, reproduce one action and inspect the state, layout, and mounted-node changes it causes. Use the browser's performance tools or the target's native profiler to measure where time is spent.
- For motion or scrolling, test realistic content, nested surfaces, and representative devices. Note the target, renderer, viewport, and build mode alongside the result.
- For unexpected activity after the interface settles, look for active transitions, clock-dependent expressions, and per-frame handlers. Distinguish ordinary frame scheduling from repeated application or rendering work.
Screenshots and event driving are useful for repeatable behavior checks. They do not establish frame rate, memory use, or accessibility. Keep those measurements separate, and compare the same workload before and after a change.
Read more
- State and Properties — reactive values and dependency management.
- Compositing and Effects — graphics layers, native elements, and effect boundaries.
- Scrolling and Viewports — content extents, scrolling, and collection costs.
- Getting Started — workstation setup, commands, and build output.
- Developer Workflow and Tools — hot reload, inspection, screenshots, and local reference.
- Public API reference — Rust types and methods.
- Runtime and cartridge design notes — historical maintainer context, including architectural directions that are not current builder-facing capabilities.
Developer Workflow and Tools
Once your project is running, most interface work follows a short loop: change the source, see the result, try an interaction, and inspect anything you cannot explain from the screen alone. Pax's development tools let you move between the running interface and the source that produced it.
Start with a project from Getting Started. Commands in this chapter run from that project's directory unless stated otherwise. The web examples need an open browser tab connected to the running application.
Hot reloading
Debug runs use Pax-only hot reload by default. Saving a .pax template
can update the mounted interface. Changing Rust application logic requires
stopping and rerunning the application unless you opt into logic reload.
The two reload lanes are independent:
| Mode | Saved .pax changes | Saved application-logic changes |
|---|---|---|
pax — default | Reload | Rebuild and restart manually |
all | Reload | Rebuild and activate automatically on supported targets |
logic | Wait for the next logic rebuild or restart | Rebuild and activate automatically on supported targets |
off | Wait for restart | Rebuild and restart manually |
Choose a mode for a run:
pax-cli run --target web --hot-reload=pax
pax-cli run --target web --hot-reload=all
pax-cli run --target web --hot-reload=logic
pax-cli run --target web --hot-reload=off
Run one of these at a time. all is useful when iterating between a template
and its Rust handlers. A logic reload still compiles application code; allow
the build and activation to finish before judging the changed behavior.
Target support and restart boundaries
Web and macOS support both lanes. iOS and iPadOS support .pax reload, while
Rust logic changes require rebuilding and relaunching. On those mobile
targets, all keeps template reload enabled and reports that saved logic
changes need a restart. logic alone is rejected as unsupported.
Some template edits also need new compiled Rust information—for example, a
new component type or handler that the running build does not know about.
Check the reload diagnostics and rebuild when necessary. Inline templates
inside .rs files follow the Rust-source change path.
Disabling a lane leaves your saved files intact. Their changes become part of
the next applicable rebuild or restart. With off, the development service
continues to support inspection; it simply stops activating source edits.
Reloading can replace component instances or the application. Do not rely on arbitrary local state, focus, or scroll position surviving every kind of reload. Reproduce important test state explicitly. A failed logic build keeps the previously active revision available; read the error, correct the source, and wait for a successful activation.
Release builds disable both lanes. Use a debug session for editing and tools, then check the actual release output before shipping. How Pax Runs explains that boundary.
Configure a project default
To store the preference with the project, add this to Cargo.toml:
[package.metadata.pax.dev]
hot_reload = "all"
The accepted values are pax, all, logic, and off. Selection precedence
is the --hot-reload flag, then the PAX_HOT_RELOAD environment variable,
then Cargo metadata, then the pax debug default. An explicit flag is useful
when checking whether a shell or project preference is affecting a run.
Generated cargo run wrappers forward trailing arguments, so
cargo run -- --hot-reload=all can select the same policy.
Format Pax source
pax-cli fmt formats .pax files and Pax templates embedded in Rust source.
It accepts a file or directory and recursively visits directories, skipping
generated and dependency folders such as .pax, target, and node_modules.
pax-cli fmt src
pax-cli fmt --check src
The first command writes formatting changes. The second checks without writing
and exits unsuccessfully if files would change, which makes it suitable for
a CI check. With no path, the command starts in the current directory.
pax-cli format is the full spelling; fmt is its alias.
Use cargo fmt for ordinary Rust formatting. Review formatting diffs as you
would other source changes; neither formatter verifies the interface's behavior.
Choose the intended session
List the live sessions on your machine, then inspect the selected one:
pax-cli dev list
pax-cli dev status
Status reports the session ID, target platform, project location, and connection details. Check these before using tools, particularly when several projects, worktrees, or browser tabs are open.
Most dev commands accept --path to locate a project's active session and
--session to select an exact session ID. The explicit ID takes precedence.
For example, from a repository checkout:
pax-cli dev status --path examples/src/increment
Project selection normally finds the active session recorded under that
project's .pax directory. If it cannot, the CLI falls back to the single live
session on the machine; with several candidates, it asks for an explicit ID.
Consequently, --path alone is not an isolation guarantee. Before an automated
source edit, check status and use the intended session ID.
Session IDs describe running sessions and can change after a restart. Re-read them instead of hardcoding one into a permanent script. Stale registrations are filtered from the live list.
Which targets expose the tools?
The session-backed inspection, selector, ray-cast, and screenshot workflows
below are implemented for web and macOS debug apps. The dev logs command
currently supports web sessions only.
iOS and iPadOS template hot reload is a separate capability; it does not imply that these local CLI session tools are available on a simulator or device. Use the native app/simulator and platform debugging tools for those checks. Release applications do not expose this development workflow.
Inspect the running tree
Start with a shallow view:
pax-cli dev inspect tree --max-depth 3
The result is JSON describing the expanded application tree. It includes
instance types, parent/child relationships, layout bounds and transforms,
layer and hit-testing flags, and available source/template identifiers.
--max-depth 0 shows only the root; omit the option when you need the full tree.
A template node can have multiple running instances, particularly inside a
loop. engine_id identifies a running instance; the containing component,
template_node_id, and source path connect it back to the template. Those
identifiers serve different purposes, and runtime IDs can change as the
interface is rebuilt. How Pax Runs
explains the expanded-tree model.
Find an element by selector
To narrow the output to Text nodes:
pax-cli dev selector 'Text'
Selectors can use a type name, an ID such as #text, or a class such as
.card. Quote them so the shell passes the selector unchanged. An ID lookup
can still return multiple instances when its template is repeated. An empty
result may mean a conditional branch is not mounted or that you selected a
different session.
On the repository's Increment example, pax-cli dev selector '#text' finds
the count label. Its response includes both the resolved native element and
the path to src/lib.pax.
Find what occupies a screen point
Capture the app at the default scale, choose a point in that image, and query
the hit stack. For a point at (640, 360):
pax-cli dev ray-cast --x 640 --y 360
Use coordinates from the app capture, not from the whole desktop or browser
window including its toolbar. The command uses default-scale dev look
coordinates; avoid taking them from a resized image. It returns the nodes
under the point in z order without clicking them. --hit-invisible also
includes nodes normally omitted from hit testing.
This is useful when a visible element does not receive input. Check the hit stack, the element's bounds, and anything layered above it. Earlier Pax siblings appear in front; Layout covers that order and the coordinate system.
Capture a frame or a sequence
Capture the current application:
pax-cli dev look
The response lists the image path, dimensions, and capture timestamp. By default, files are written into a request-specific capture directory inside the selected development session. You can choose an output directory:
pax-cli dev look --output-dir captures/before
Capture files use sequential names such as 0000.png. Use a different output
directory for each observation you want to keep: reusing the same directory
can overwrite earlier captures.
For a five-second observation with a nominal half-second interval:
pax-cli dev look --period-ms 500 --duration-ms 5000 --output-dir captures/motion
Start the observation, then perform the interaction in the app. Both timing options are required for a sequence. Capture work and application scheduling affect when samples arrive, so use the returned timestamps and do not assume a fixed image count. This is useful for inspecting states through an animation; it is not a video recording or a frame-rate measurement.
--scale 0.5 requests smaller images. PNG is the default; --format jpeg
selects JPEG and accepts an optional --quality value from 0.0 to 1.0.
Scaling the capture does not resize the application's viewport.
Web capture combines rendered layers and native content inside the Pax mount. An unpainted background can remain transparent. Background tabs use fallback paths for some native text and controls, so compare the image with the live foreground app when inspecting fine rendering or platform-widget details. Screenshots do not replace keyboard, assistive-technology, or real-device checks.
Read logs and diagnose a reload
For a running web session:
pax-cli dev logs --limit 50
pax-cli dev logs --follow
--follow continues until interrupted. The log reader reports browser messages
captured by the development session; compiler/build errors remain in the
terminal that runs the app. A quiet session may return no entries.
Rust logging on the web defaults to warnings in debug and errors in release.
For more detail during development, add ?pax_log=info to the app's local URL
and reload it. If the URL already has a query, add &pax_log=info instead.
The supported levels are error, warn, info, debug, and trace.
At info, renderer diagnostics identify WebGPU or Piet and related graphics
configuration. The same messages are available in the browser console.
Verbose output can be noisy, and captured logs are bounded recent history;
collect the relevant messages while reproducing the problem.
Drive events and check the result
Exercise the interface through its userland input path: click or tap a control, type into a native field, scroll its viewport, or use the keyboard. For repeatable browser tests, a browser automation tool can perform those same actions while Pax's inspector and captures help verify the result. Canvas geometry often needs a coordinate-based action; native controls may expose semantic browser targets.
Current CLI boundary: pax-cli dev does not yet provide a command to inject
a click, tap, or key event. ray-cast only observes hit targets. dev touch
changes source, as described below; it is not a touch-input driver. The runtime's
event handling remains part of Pax's open-source implementation, but a
dedicated CLI gesture interface is still a tooling gap.
Use Event Handling to understand event binding,
propagation, and handler behavior. NodeContext::dispatch_event delivers named
application events; it does not emulate a platform input gesture.
Source edits through developer tools
For normal authoring, edit files in your editor and let the chosen reload
policy apply. Two dev touch operations also expose source changes to tools:
apply-component-sourcereplaces a component's entire.paxsource file.replace-nodereplaces a template node with a Pax subtemplate and reports the affected source and reload scope.
Both are mutations of the project, not temporary changes to pixels. Review the working-tree diff and target the intended session before using them. Replacing a repeated template node can affect all of its instances. An empty replacement subtemplate deletes the targeted node.
For example, to replace a component named Example, first prepare a complete
replacement file and review it. Then use apply-component-source with
--component Example and --source-file pointing to that file. Supply the
session ID verified by dev status. The command parses the source before
writing, and refuses ambiguous component-name matches; valid syntax alone
does not guarantee that the running build contains every referenced type.
Use pax-cli dev touch --help and the selected subcommand's --help for exact
arguments. A successful source write does not override a disabled reload
lane. After editing, confirm the activation and inspect the actual result.
Read docs and example source locally
The installed CLI contains a documentation and example-source snapshot:
pax-cli docs
pax-cli docs search 'hot reload | Scroller' --limit 5
pax-cli docs open template-language
pax-cli docs examples --list
pax-cli docs examples increment
docs lists the available articles and references. Search uses | for
alternatives; quote the query so your shell does not treat it as a pipeline.
open accepts a slug, path, or title and displays the article in a terminal
pager. Example lookup prints the included source files.
These reads use the CLI's bundled snapshot, not a live fetch of the latest
website. Check pax-cli --version when comparing instructions across releases.
Viewing source through docs examples does not create a project or install
its assets. To create a runnable project from the curated bundled set, use
create --example below.
In a repository checkout, canonical examples live under
examples/src; run one from the repository root with:
pax-cli run --path examples/src/increment --target web
The CLI also has docs build for contributors rebuilding the documentation
assets in a Pax repository. It can regenerate API pages, example metadata/
bundles, and the CLI's search content. It is not required to browse installed
docs, and should not be run casually over an in-progress documentation edit.
The HTML docs use optimized release builds for their interactive examples. An example runs automatically while its stage is visible and suspends frame updates and drawing when scrolled out of view or when the docs tab is hidden. Returning to it preserves its state; Restart starts over, and Open standalone runs the example independently. Suspension does not stop application network requests or other external work. Elapsed-time-based animations can advance to the current time when resumed.
Create from a bundled example
The CLI includes complete project templates for a curated subset of examples:
| Example | Starting point |
|---|---|
living-quilt (default) | Interactive geometric tapestry with responsive components, motion, lighting, and GPU alpha masks |
ink-and-light | A responsive postcard with handwriting, local assets, and Rust interaction |
increment | A minimal counter for exploring the reactive update loop |
pax-cli create my-quilt
pax-cli create my-postcard --example=ink-and-light
pax-cli create my-counter --example=increment
Choose one, enter its directory, and run pax-cli run --target web. These
projects include the source and assets needed to build; they require no Pax
repository checkout. An unknown example name reports the available choices
without creating a partial project. The curated create set is smaller than
the source-reading catalog in docs examples.
Each CLI release carries its own snapshot. For contributors, examples/src/*
is canonical; examples/bundled-cli-examples.toml selects the create set and
scripts/sync-cli-examples.py synchronizes the packaged archive. Release tooling
runs that synchronization after version updates and before packaging.
Read more
- Getting Started — installation and the first run.
- Templates — the source structure behind the scene.
- Event Handling — input and application logic.
- Animation and Motion — declarative transitions and timelines.
- How Pax Runs — runtime structure and performance reasoning.
Targets, Build, and Deployment
A running development project is the starting point. To put it in someone else's hands, choose a target, build its artifacts, and check those artifacts in the environment where people will use them.
Pax targets web, macOS, iOS, and iPadOS. Web releases can be served as static files. macOS has a release build path; distribution adds Apple's signing and packaging steps. iOS and iPadOS support debug and release builds, including local simulator and connected-device runs. Apple distribution requires additional signing and delivery steps.
This chapter assumes you have completed Getting Started. Commands run from your project's directory unless stated otherwise.
Targets and workstations
The target is where the application runs. The workstation is where you compile it.
| Application target | CLI value | Development workstation | Required target tools |
|---|---|---|---|
| Web browser | web | macOS, Debian/Ubuntu Linux, or Windows | Rust, OS build tools, wasm32-unknown-unknown, and wasm-pack |
| macOS application | macos | macOS | Rust, full Xcode, and the selected Apple Rust targets |
| iPhone application | ios | macOS | Rust, full Xcode, iOS SDK/simulator, and the selected Apple Rust target |
| iPad application | ipados | macOS | The same Apple toolchain, with an iPad simulator or device |
ipad is also accepted as an alias for ipados. Linux and Windows support
the web development workflow; they are not native application targets in the
current CLI. See workstation setup
for installation commands and Apple preparation
for additional native requirements.
The published CLI includes its web interface bundle. Node.js/npm are needed when rebuilding that interface from the Pax repository, rather than for the ordinary installed-CLI workflow.
Target support does not promise identical rendering or controls everywhere. How Pax Runs explains backend selection; feature chapters state their narrower target and backend limits. Include the browsers, devices, native controls, and rendering effects your application actually uses in its test plan.
Run, build, and release
Use run while developing. Use build when you want output without launching
the application:
pax-cli run --target web
pax-cli build --target web
pax-cli build --target web --release
Run these separately. run defaults to a debug build and starts the target's
development harness. build also defaults to debug; --release selects the
optimized release path. iOS and iPadOS additionally accept run --release to
install and launch a local release. For web and macOS, use build --release.
You can work from another directory with --path:
pax-cli build --path path/to/my-project --target web --release
Release builds exclude the development service and hot-reload machinery. Test the release itself: an application that works in a live development session still needs a release check. Read Debug and release for the runtime differences, and Developer Workflow for reload configuration.
Where the output goes
Pax writes generated files beneath the project's .pax/ directory:
| Build | Output |
|---|---|
| Web debug | .pax/build/debug/web/ |
| Web release | .pax/build/release/web/ |
Web --profiling | .pax/build/profiling/web/ |
| macOS debug | .pax/build/debug/macos/app/Pax macOS (Development).app |
| macOS release | .pax/build/release/macos/app/Pax macOS (Release).app |
| iOS debug | .pax/build/debug/ios/app/Pax iOS (Development).app |
| iPadOS debug | .pax/build/debug/ipados/app/Pax iOS (Development).app |
| iOS release | .pax/build/release/ios/app/Pax iOS (Development).app |
| iPadOS release | .pax/build/release/ipados/app/Pax iOS (Development).app |
iOS and iPadOS share the generated iOS host, including that bundle filename. The mobile bundle filename retains “Development” even when built with the Release configuration. The display name inside the app can be configured independently.
--profiling is web-only: it produces optimized output with Wasm names retained
for size analysis. It is separate from the normal release directory. See
How Pax Runs for performance investigation.
Build output is replaceable. Keep authored changes in project source, assets,
metadata, or an explicitly customized interface. If you use pax-cli clean,
it removes the project's entire .pax/ directory, including builds and local
development artifacts. Save anything you need before cleaning.
Build a web release
From the project root:
pax-cli build --target web --release
A successful build prints the output location. A typical directory contains:
.pax/build/release/web/
├── index.html
├── pax-interface-web.js
├── pax-interface-web.css
├── pax-cartridge.js
├── pax-cartridge_bg.wasm
├── snippets/
├── assets/
└── … favicon, public files, and supporting files
Keep the directory together. The HTML loads the interface and cartridge, and
the cartridge depends on its matching Wasm and supporting files. Uploading
only index.html or only the .wasm file is insufficient.
Preview the release locally
Serve the output over HTTP rather than opening index.html as a file: URL.
For example, if Python 3 is installed:
python3 -m http.server 8080 --bind 127.0.0.1 --directory .pax/build/release/web
On Windows, py -3 can replace python3. Open
http://127.0.0.1:8080/, interact with the app, and inspect the browser console
and network requests. This server is for local review. It does not configure
production HTTPS, caching, or application-route fallback.
Suspend an embedded web app
A custom web host can call window.Pax.setSuspended(true) to suspend frame
updates and drawing without unmounting the app. Call it with false to resume.
For a same-origin iframe, the host can access the API through
iframe.contentWindow.Pax; wait for the iframe's load event first.
Loading the iframe with ?pax_suspended=1 starts it suspended, including while
its Wasm is loading. The standalone URL needs no such parameter.
Use an IntersectionObserver on the iframe's stage and the host document's
visibilitychange event to resume only while the example is in view and the
host tab is visible. The docs use this arrangement. Properties and form state
remain mounted; this does not cancel application requests, freeze wall-clock
time, or pause arbitrary JavaScript timers. Elapsed-time-based animations may
catch up when frame updates resume.
Assets and web public files
Use assets/ for application media. Pax copies those assets into the target's
build. Images and fonts still need suitable source paths and loading behavior;
Text, Fonts, and Images owns those authoring details.
Remote resources remain network dependencies unless explicitly bundled or
vendored by the relevant target build path.
Web public files
Create public/ beside Cargo.toml for web files that should be served as
ordinary HTTP responses. Relative paths and bytes are preserved:
public/ai.md → /ai.md
public/robots.txt → /robots.txt
public/.well-known/pax → /.well-known/pax
public/guide/index.html → /guide/
Those URLs assume deployment at the site's root. A build copies these files
into its web output. During pax-cli run, the server reads the source
public/ directory directly, so edits, additions, and deletions become visible
after a browser refresh without restarting Pax.
Public files cannot overwrite generated or customized interface files.
public/index.html conflicts with the application's entry page; the top-level
assets, snippets, and __reloads__ directories are reserved. Symbolic links
are rejected. Review everything placed here: these files are intended to be
public, including dot-prefixed files such as .well-known entries.
The hosting server determines response headers and content types; copying a
public file does not configure those policies.
Use public/ for web-only documents and files such as robots.txt. It is not
packaged as an Apple application's asset directory. For titles, descriptions,
and social previews tied to application routes, use
web route metadata. The compiler generates
those entry documents from the route declarations. Avoid a public-file path
that conflicts with a generated route entry.
Deploy web
A web build needs a static host. It does not need a running Pax CLI on the
server. For a first deployment, serve the app at the root of a domain or
subdomain, such as https://app.example.com/.
- Build and test
.pax/build/release/web/locally. - Publish that directory's contents as the site's document root, including
snippets/, assets, and any dot-prefixed public files. - Enable HTTPS and correct content types on the host.
- Open the deployed URL in a fresh browser session. Test interaction, media, and any direct route URLs before sharing it.
If a hosting service accepts a prebuilt directory, choose this web output as
its publish directory. Do not upload the project root, Cargo.toml, or the
whole .pax/ directory. If a service builds from source, its build environment
also needs the web prerequisites; a generic
JavaScript-only build image is not enough.
A static-server configuration
For an existing NGINX installation, this is a minimal document-root example.
/srv/pax/my-app stands for a directory containing the complete release output;
adjust it and the MIME include path for your server. The enclosing http block
and public HTTPS configuration belong to the server administrator.
server {
listen 127.0.0.1:8080;
server_name localhost;
root /srv/pax/my-app;
index index.html;
include /etc/nginx/mime.types;
add_header Cache-Control "no-cache";
location / {
try_files $uri $uri/ =404;
}
}
Here, an existing file or directory is served; an unknown path returns 404.
Check the configuration with nginx -t before applying it. See NGINX's
static-content guide
for how root, index, and try_files work. This example is a starting point
for a configured host, not a command to expose a development server publicly.
Direct links and the base URL
An in-app navigation can work even when a fresh request to the same URL fails.
When someone opens /teams/42/settings, the server receives that path before
Pax starts. There are two separate requirements:
- The server must serve the app's entry HTML for application routes that have no corresponding static file.
- The HTML must still load scripts, Wasm, styles, and media from the correct build directory.
The web build prepares the entry documents' base URL from
[package.metadata.pax.web].site_url, using / when no site URL is supplied.
For a domain-root application, the generated HTML includes:
<base href="/">
The default bootstrap uses document.baseURI, so a nested entry can load the
same bundle as the root entry. No interface ejection is needed for this
standard routing setup. HTML's
base element
also affects other relative document URLs, including fragment links; review
any custom interface HTML alongside the app. The metadata pass sets its base
as well. See Routing for the declarations and
generated route-metadata.json and route-specific HTML files.
Then configure fallback for the application's route space. For example, an
NGINX app with routes beneath /teams/ can add:
location /teams/ {
try_files $uri $uri/ /index.html;
}
Keep real public documents and assets ahead of application fallback. Missing JS, CSS, Wasm, and media should return 404 rather than the app's HTML. A blanket fallback can conceal a missing file behind a script or MIME error.
Serve generated route entries ahead of the fallback. Preserve the requested
URL rather than redirecting it to /; the app needs that path to choose its
content. The default route controls the in-app not-found screen, while the
server controls HTTP status codes.
A site_url containing /my-app/ supplies that HTML base, but does not strip
the prefix from the pathname delivered to Router. The metadata matcher also
sees the browser pathname. Do not treat this setting as an end-to-end router
mount-prefix option: route definitions, generated entry locations, canonical
URLs, and host mapping all need to agree. Prefer a domain-root deployment for
the launch path, and verify those pieces separately before using a subdirectory.
See Routing for nested scope, parameters, and navigation.
Embedded examples
The default web interface supports an explicit query-backed routing mode for
examples hosted inside another site. Open its entry document with pax_route=/
(URL-encoded as ?pax_route=%2F) to start at the application root. In this mode,
Pax loads bundle resources beside that entry document and stores subsequent
same-tab routes, including their query and fragment, inside pax_route.
Back, Forward, and reload keep the physical entry URL intact. The docs use this
mode for both embedded examples and their Open standalone links.
Omit the parameter for normal pathname routing. Query-backed examples do not need a server fallback for each application route, but this is a preview/host integration mode, not a replacement for the crawler-visible route entries described above. A custom ejected web interface must preserve the default bootstrap's embedded-base setup if it uses this mode.
HTTPS, content types, and caching
Serve production apps over HTTPS. WebGPU requires a secure context; loopback development URLs have special treatment. Backend availability still depends on the browser and device. See MDN's WebGPU reference.
Return .wasm with Content-Type: application/wasm, and JavaScript/CSS with
their appropriate types. The Wasm MIME type enables streaming compilation.
MDN documents this requirement.
Configure transfer compression on the host if desired: the CLI's gzip-size
report is a measurement, not proof that the server sends compressed responses.
Pax's main output filenames are stable across builds. For files updated at the
same URL, a conservative starting policy is Cache-Control: no-cache, allowing
storage but requiring revalidation. Reserve long-lived immutable caching for
versioned or content-addressed URLs whose contents will never change. See
HTTP caching.
Deploy matching HTML, JavaScript, Wasm, and assets together. Retain the previous complete release for rollback, and test an upgrade with an already-open app as well as a fresh load. A versioned asset layout can keep old and new clients from fetching incompatible files; it requires an explicit URL/hosting plan, since Pax does not automatically hash all of these filenames.
Build for Apple platforms
Prepare the Apple toolchain
Install full Xcode and the platform components you need. Command Line Tools alone are enough for some web prerequisites, but do not provide the complete native app/simulator workflow. Confirm the active installation:
xcode-select -p
xcodebuild -version
rustup target list --installed
Complete Xcode's initial setup and select the intended Xcode installation if these commands report only a Command Line Tools directory. Install the Rust targets needed for the build you plan to run:
| Build destination | Rust target |
|---|---|
| macOS on Apple silicon | aarch64-apple-darwin |
| macOS on Intel | x86_64-apple-darwin |
| iPhone or iPad device | aarch64-apple-ios |
| iOS/iPadOS simulator on Apple silicon | aarch64-apple-ios-sim |
| iOS/iPadOS simulator on Intel | x86_64-apple-ios |
For example, on an Apple-silicon Mac preparing for a macOS release and mobile simulator/device development:
rustup target add aarch64-apple-darwin x86_64-apple-darwin
rustup target add aarch64-apple-ios-sim aarch64-apple-ios
macOS debug builds select the workstation's architecture. The current macOS release path builds both Apple-silicon and Intel code, so it needs both targets. Mobile simulator builds select the host simulator architecture; a physical device build uses the device target instead.
Use the project's default Cargo target/ directory for Apple builds. The
current packaging code looks there for the compiled cartridge; a custom
CARGO_TARGET_DIR can let Rust compilation finish and then fail during packaging
because the expected library file is elsewhere.
macOS
pax-cli run --target macos
pax-cli build --target macos
pax-cli build --target macos --release
run builds and opens the development app. A build also prepares the Xcode
project and Swift packages under .pax/build/<mode>/macos/, alongside the
app/ output. A release .app is the input to your distribution process;
it is not evidence that signing, notarization, or store submission is complete.
iOS and iPadOS development
Run against an available simulator:
pax-cli run --target ios
pax-cli run --target ipados
The default selector chooses an appropriate phone or tablet simulator. List
available simulators with xcrun simctl list devices available, then select
one by name or UDID when needed:
pax-cli run --target ipados --ios-device "simulator:YOUR_SIMULATOR_UDID"
Replace the placeholder with an actual identifier. The --ios-device flag
applies to both mobile targets. It accepts simulator, device, prefixed
names/UDIDs such as device:…, or an exact device name/UDID. Specify a device
when several are connected.
For a physical-device development run, connect and trust the device, enable Developer Mode where required, and configure an Apple development team in Xcode and the project:
pax-cli run --target ios --ios-device device --ios-development-team YOUR_TEAM_ID
Use your real Team ID. This path may ask Xcode to update provisioning or
register the selected device. The flag overrides the target's
development_team metadata. Signing errors need to be resolved through your
Xcode account, app identity, and provisioning configuration.
To build a debug simulator app without launching it, use
pax-cli build --target ios or pax-cli build --target ipados. Device run
and simulator run are development workflows; they do not produce a TestFlight
or App Store upload.
Local release runs
Use the same simulator selection with --release to build and launch optimized
code without designtime or hot reload:
pax-cli run --target ios --release
pax-cli run --target ipados --release --ios-device "simulator:YOUR_SIMULATOR_UDID"
Run either command for the target you want. Simulator runs do not require signing credentials. For a paired physical device with Developer Mode enabled:
pax-cli run --release --target ipados \
--ios-device 'device:My iPad' --ios-development-team YOUR_TEAM_ID
Use --target ios for iPhone and replace the device name and team with your
own. The CLI compiles, uses Xcode's Release configuration with Apple Development
signing, installs, and launches the app. The team can also come from
project metadata. Release runs disable both hot-reload
lanes even if a flag, environment variable, or project setting requests them.
pax-cli build --target ios --release (or --target ipados) builds the
device-target app without launching it. These commands do not archive, export,
or upload an app for TestFlight or the App Store.
Project metadata
Keep application identity and packaging preferences in Cargo.toml. Pax reads
[package.metadata.pax] while preparing the target interface:
[package.metadata.pax]
title = "My App"
icon = "assets/app-icon.png"
[package.metadata.pax.web]
title = "My App on the Web"
favicon = "assets/favicon.png"
[package.metadata.pax.ios]
bundle_identifier = "com.example.myapp"
development_team = "YOUR_TEAM_ID"
build_number = "1"
[package.metadata.pax.ios.info_plist]
NSCameraUsageDescription = "Take a photo to attach to a note."
[package.metadata.pax.macos]
bundle_identifier = "com.example.myapp.macos"
Use identifiers and a team belonging to your project. Include icon/favicon keys only when those source files exist. Add permission descriptions that truthfully describe the capabilities your application uses.
Target-specific values override common values. iPadOS first checks
[package.metadata.pax.ipados], then iOS, then common metadata. File paths are
relative to the project root unless absolute; project-relative paths keep the
configuration portable between workstations.
| Key | Applies to | Meaning |
|---|---|---|
title | Common, web, Apple | Browser title or Apple display name; defaults to the Cargo package name |
icon | Common, web, Apple | Image used for generated icons, including the fallback web favicon |
favicon | Web | Explicit favicon file; takes priority over icon-derived favicon generation |
site_name | Web | Open Graph site name; defaults to the resolved web title |
site_url | Web | Absolute public HTTP(S) URL, without query or fragment; required for release builds with indexable concrete routes |
social_image | Web | Default social-preview image: an absolute HTTP(S) URL or a path relative to site_url |
social_image_alt | Web | Description of the social image; configure both image fields together |
bundle_identifier | Common, Apple | Application identity for the Apple host |
marketing_version | Common, Apple | User-facing version; defaults to Cargo package.version |
build_number | Common, Apple | Apple build number, expressed as a TOML string |
development_team | Common, Apple | Apple development team used during signing |
info_plist | Common, Apple | Nested table of string-valued Info.plist entries |
info_plist supports strings here, not arbitrary arrays, dictionaries, or
booleans. Platform-specific entries override matching common keys; iPadOS
also inherits iOS entries. Reload configuration has its own
[package.metadata.pax.dev] table.
For per-page titles, descriptions, indexing, and social-preview overrides, use Web route metadata. These site-wide settings supply defaults and public URLs; they do not enumerate parameterized pages.
Application icons
Use a square source image. iOS and iPadOS require an opaque image and generate
a single 1024×1024 AppIcon asset. macOS generates its AppIcon size set from
the square source. Without an explicit web favicon, a configured icon produces
a 64×64 PNG favicon. These are build-time transformations; rebuild after
changing the source or metadata.
The bundled iOS/iPadOS interface includes a Pax icon when no custom icon is configured. An ejected interface retains its own asset catalog unless an icon override is supplied.
Apple distribution
For macOS, finish the signing and distribution configuration appropriate to your audience. Direct distribution commonly uses Developer ID signing and notarization; store distribution follows the App Store process. Apple's distribution guide and notarization guide own those platform steps. Pax does not upload an archive or notarize the application for you.
For iOS and iPadOS, the CLI completes the local release app build and can development-sign and launch it on a selected device. Distribution still requires the appropriate Apple archive/export, distribution signing, provisioning, and upload workflow. A successful local release run does not establish TestFlight or App Store readiness.
The generated mobile Xcode project is currently at
.pax/interface/ios/pax-app-ios/pax-app-ios.xcodeproj; shared Swift packages
and cartridge framework files are under .pax/interface/common/. These are
generated working files and can be overwritten by later builds.
A custom native host or manual Xcode integration requires its own release, resource, signing, and device validation. There is no verified end-to-end mobile distribution recipe in this chapter yet. If TestFlight or App Store delivery is required for your project, plan and verify that distribution step separately from the CLI's local release workflow.
Before sharing a build
- Test the release output, including a fresh launch and its primary interaction.
- Check assets and remote dependencies from the deployed location; keep private
files and credentials out of the build and
public/. - Open routed URLs directly, refresh them, and try back/forward navigation.
- Verify the actual target/browser's rendering and native-control behavior.
- Check application title, icon, version, and Apple identity/signing where applicable.
- Keep one complete previous release and a repeatable build procedure.
The shortest shipping loop is a small one: build, serve or install the artifact, exercise the important behavior, and repeat that check after deployment.
Read more
- Getting Started — installation and a first successful run.
- Developer Workflow and Tools — editing, reload, and inspection.
- Routing — URL-driven UI and navigation.
- Text, Fonts, and Images — media sources and loading behavior.
- How Pax Runs — runtime, rendering backends, and release behavior.
API Reference
Use the API reference to look up a type, inspect a component's properties, or check a method signature while building. The pages are generated from Rust declarations and documentation comments in the Pax source tree. The guide chapters explain the concepts and show how to combine these APIs into an interface.
If you are starting a project, begin with Getting Started. For an existing project, match the documentation version to your Pax dependency. The development reference may contain changes that are absent from your release; a newer reference page does not establish support in an older build.
Find an API
| What you are working on | Reference | Guide |
|---|---|---|
| Reactive state and computed values | Properties | State and Properties |
| Input events and handler arguments | Events | Events and Rust |
| Text and fonts | Text | Text, Fonts, and Images |
| Shapes, paths, and paint | Drawing values and drawing elements | Drawing and Styling |
| Routes and navigation | Router | Routing |
Read a reference page
A type's Rust declaration shows its field and method types. For a Pax element,
the guide supplies the corresponding template usage: a Rust field such as
Property<String> is an input you can set with a compatible literal or binding.
Templates and
PAXEL explain that authoring syntax.
Read the documentation alongside the signature for defaults, behavior, and target restrictions. An item appearing in the reference does not establish that every backend or platform implements it equally. The relevant guide and source for your release provide the context; the targets chapter explains the build boundary.
Public crates
pax-runtime-api contains author-facing values and reactive APIs. pax-std
contains the standard elements and components used in templates. Browse their
modules when you need more detail than the entry points above:
Engine and maintainer APIs
For primitive implementations and work on Pax itself, continue with the Maintainer Reference. It separates current internal APIs from historical architecture and design notes.
pax-runtime-api
Public runtime API types shared across Pax crates, user components, and platform backends.
Most names are reexported at the crate root for compatibility, while their source modules provide the browsing ontology used by the generated API docs.
Submodules
- animation
- color
- cursor
- drawing
- events
- layout
- math
- pax_value
- platform
- properties
- rendering
- store
- transform
- unit_value
- variables
Macros
impl_default_coercion_rule
Implements coercion by converting the selected PaxValue variant's contents.
The optional third argument opts into CoercionRules::is_identity_roundtrip;
omit it unless the type satisfies that method's exact, side-effect-free contract.
animation
Animation primitives: interpolation, easing curves, transition queues, and timelines. See also: properties.ease_to and properties.ease_to_later.
Structs
Timeline
Minimal timeline state used by animation-oriented controls.
Properties
playhead_position
Type: usize
Current playhead frame.
frame_count
Type: usize
Total number of frames.
is_playing
Type: bool
Whether the timeline is currently advancing.
Enums
Duration
A duration used by animation and transition systems.
Frames preserves Pax's historical frame-count semantics, while
Milliseconds and Seconds advance from the chassis-provided monotonic
wall clock.
Variants
Frames(Numeric)
Duration measured in runtime frames.
Milliseconds(Numeric)
Duration measured in milliseconds.
Seconds(Numeric)
Duration measured in seconds.
Implementations
as_frames_f64
pub fn as_frames_f64(&self) -> f64
Converts the duration to frames, using 60fps as the nominal conversion rate when converting wall-clock units for mixed-unit authoring.
as_milliseconds_f64
pub fn as_milliseconds_f64(&self) -> f64
Converts the duration to milliseconds, using 60fps as the nominal frame duration when converting frame units for mixed-unit authoring.
is_frame_based
pub fn is_frame_based(&self) -> bool
Returns true when the duration is expressed in frame units.
raw_value
pub fn raw_value(&self) -> f64
Raw value in this duration's own unit.
EasingCurve
Pre-built easing curves for use in transitions, as well as a Custom variant that can be used to
specify an arbitrary easing curve via a function f: f64 -> f64 mapping a time on the unit interval to a multiplier on the unit interval.
Variants
Linear
A linear easing curve, where the interpolated value changes at a constant rate over time.
Hold
A hold easing curve, where the interpolated value remains constant until the end of the transition, at which point it jumps to the final value.
InQuad
A quadratic easing curve where the interpolated value starts slow and accelerates towards the end of the transition.
OutQuad
A quadratic easing curve where the interpolated value starts fast and decelerates towards the end of the transition.
InOutQuad
A quadratic easing curve where the interpolated value starts slow, accelerates towards the middle of the transition, and then decelerates towards the end of the transition.
InBack
A back easing curve where the interpolated value starts by briefly moving in the opposite direction before accelerating towards the final value.
OutBack
A back easing curve where the interpolated value overshoots the final value before settling back to it.
InOutBack
A back easing curve where the interpolated value starts by briefly moving in the opposite direction, then accelerates towards the final value, overshooting it before settling back to it.
Custom(Box<dyn Fn(f64) -> f64>)
A custom easing curve defined by a user-provided function that maps a time on the unit interval to a multiplier on the unit interval.
Implementations
interpolate
pub fn interpolate<T: Interpolatable>(&self, v0: &T, v1: &T, t: f64) -> T
Interpolates between v0 and v1 using t as time on the unit interval.
Traits
Interpolatable
Marks a value as able to participate in Pax property transitions.
color
Color and opacity types used by Pax style properties and render backends.
Structs
Percent
Raw Percent type, which we use for serialization and dynamic traversal. At the time
of authoring, this type is not used directly at runtime, but is intended for into coercion
into downstream types, e.g. ColorChannel, Rotation, and Size. This allows us to be "dumb"
about how we parse %, and allow the context in which it is used to pull forward a specific
type through into inference.
Properties
0
Type: Numeric
Enums
Color
Entrypoint for specifying and representing colors in Pax.
Variants
rgb(ColorChannel, ColorChannel, ColorChannel)
Models a color in the RGB space, with an alpha channel of 100%
rgba(ColorChannel, ColorChannel, ColorChannel, ColorChannel)
Models a color in the RGBA space
hsl(Rotation, ColorChannel, ColorChannel)
Models a color in the HSL space, with an alpha channel of 100%
hsla(Rotation, ColorChannel, ColorChannel, ColorChannel)
Models a color in the HSLA space.
SLATE
Cool blue-gray, RGB (100, 116, 139).
GRAY
Balanced gray, RGB (107, 114, 128).
ZINC
Crisp cool gray, RGB (113, 113, 122).
NEUTRAL
Plain neutral gray, RGB (115, 115, 115).
STONE
Warm stone gray, RGB (120, 113, 108).
RED
Bright warm red, RGB (239, 68, 68).
ORANGE
Vivid citrus orange, RGB (249, 115, 22).
AMBER
Golden amber, RGB (245, 158, 11).
YELLOW
Sunny yellow, RGB (234, 179, 8).
LIME
Electric lime, RGB (132, 204, 22).
GREEN
Fresh green, RGB (34, 197, 94).
EMERALD
Jewel emerald, RGB (16, 185, 129).
TEAL
Deep aquatic teal, RGB (20, 184, 166).
CYAN
Bright clean cyan, RGB (6, 182, 212).
SKY
Open sky blue, RGB (14, 165, 233).
BLUE
Saturated primary blue, RGB (59, 130, 246).
INDIGO
Cool electric indigo, RGB (99, 102, 241).
VIOLET
Soft vivid violet, RGB (139, 92, 246).
PURPLE
Rich playful purple, RGB (168, 85, 247).
FUCHSIA
Brilliant magenta fuchsia, RGB (217, 70, 239).
PINK
Bright candy pink, RGB (236, 72, 153).
ROSE
Warm rosy red, RGB (244, 63, 94).
BLACK
Pure black, RGB (0, 0, 0).
WHITE
Pure white, RGB (255, 255, 255).
TRANSPARENT
Fully transparent white, RGB (255, 255, 255).
NONE
Non-rendering transparent, RGB (255, 255, 255).
Implementations
alpha_0_1
pub fn alpha_0_1(&self) -> f64
Returns this color's alpha channel normalized to the [0.0, 1.0] range.
from_hex
pub fn from_hex(hex: &str) -> Self
Constructs a color from a six- or eight-character RGB/RGBA hex string.
from_rgba_0_1
pub fn from_rgba_0_1(rgba_0_1: [f64; 4]) -> Self
Constructs a color from normalized RGBA channels in the [0.0, 1.0] range.
hsl
pub fn hsl(h: Rotation, s: ColorChannel, l: ColorChannel) -> Self
Constructs an HSL color with 100% alpha.
hsla
pub fn hsla(h: Rotation, s: ColorChannel, l: ColorChannel, a: ColorChannel) -> Self
Constructs an HSLA color.
rgb
pub fn rgb(r: ColorChannel, g: ColorChannel, b: ColorChannel) -> Self
Constructs an RGB color with 100% alpha.
rgba
pub fn rgba(r: ColorChannel, g: ColorChannel, b: ColorChannel, a: ColorChannel) -> Self
Constructs an RGBA color.
to_hsla_0_1
pub fn to_hsla_0_1(&self) -> [f64; 4]
Returns HSLA channels normalized to the [0.0, 1.0] range.
to_rgba_0_1
pub fn to_rgba_0_1(&self) -> [f64; 4]
Returns RGBA channels normalized to the [0.0, 1.0] range.
with_alpha_factor
pub fn with_alpha_factor(&self, factor: f64) -> Self
Multiplies this color's alpha channel by factor.
ColorChannel
Describes a color channel in a unit appropriate to the surrounding color model.
Variants
Rotation(Rotation)
Used, for example, to express hue in HSL or other rotational color models.
Integer(u8)
Integer color channel in the [0, 255] range.
Percent(Numeric)
Percent color channel in the [0.0, 100.0] range.
Implementations
to_float_0_1
pub fn to_float_0_1(&self) -> f64
Normalizes this color channel as a float in the [0.0, 1.0] range.
Opacity
Describes an opacity value either as normalized alpha or as a percent.
Variants
Alpha(Numeric)
Unitless alpha in the normalized [0.0, 1.0] range.
Percent(Numeric)
Percent alpha in the [0.0, 100.0] range.
Implementations
to_float_0_1
pub fn to_float_0_1(&self) -> f64
Normalizes this Opacity as a float in [0.0, 1.0].
cursor
Enums
CursorStyle
The variants of mouse cursors available on mouse-supporting platforms (derived from CSS cursor values).
Variants
Auto
Default
None
ContextMenu
Help
Pointer
Progress
Wait
Cell
Crosshair
Text
VerticalText
Alias
Copy
Move
NoDrop
NotAllowed
AllScroll
ColResize
RowResize
NResize
EResize
SResize
WResize
NeResize
NwResize
SeResize
SwResize
EwResize
NsResize
NeswResize
NwseResize
ZoomIn
ZoomOut
Grab
Grabbing
Url(String, i32, i32)
drawing
Vector drawing primitives: paths, fills, strokes, caps, and gradients.
Submodules
Structs
Depth
Logical scene depth for lighting calculations.
Depth is expressed in logical pixels. Unlike [Size], it is not anchored
to a viewport or parent box, so percent and combined units are intentionally
rejected during value coercion.
Properties
0
Type: Numeric
Implementations
to_float
pub fn to_float(&self) -> f64
Returns the depth as a floating-point logical pixel value.
GradientStop
A color stop for a gradient fill, defined by a position (% or px) and a color.
Properties
position
Type: Size
Stop position, conventionally expressed as a percentage along the gradient.
color
Type: Color
Color at this stop.
Implementations
get
pub fn get(color: Color, position: Size) -> GradientStop
Constructs a gradient stop at position.
with_alpha_factor
pub fn with_alpha_factor(&self, factor: f64) -> GradientStop
Returns a copy of this stop with alpha multiplied by factor.
LinearGradient
Describes a linear gradient fill with a start and end point, and a list of color stops.
Pax templates canonically author each point as [x, y]: magic index 0
is the horizontal coordinate and index 1 is the vertical coordinate.
Properties
start
Gradient start point in the primitive's local coordinate space.
end
Gradient end point in the primitive's local coordinate space.
stops
Type: Vec<GradientStop>
Ordered color stops along the gradient.
MaterialParams
Tunable response parameters for a light-reactive vector material.
Properties
ambient
Type: Property<f64>
Ambient contribution multiplier.
diffuse
Type: Property<f64>
Diffuse contribution multiplier.
specular
Type: Property<f64>
Specular contribution multiplier.
roughness
Type: Property<f64>
Surface roughness in the [0.0, 1.0] range.
metallic
Type: Property<f64>
Metallic response in the [0.0, 1.0] range.
emissive
Additive emissive color.
emissive_intensity
Type: Property<f64>
Additive emissive intensity.
RadialGradient
Describes a radial gradient fill with a start and end point, a radius, and a list of color stops.
Pax templates canonically author each point as [x, y]: magic index 0
is the horizontal coordinate and index 1 is the vertical coordinate.
Properties
end
Outer radius endpoint in the primitive's local coordinate space.
start
Gradient center point in the primitive's local coordinate space.
radius
Type: f64
Radial gradient radius.
stops
Type: Vec<GradientStop>
Ordered color stops along the gradient.
SceneAmbientLight
Resolved ambient light for one logical canvas layer.
Properties
color
Type: Color
Ambient color.
intensity
Type: f64
Ambient intensity.
SceneLight
Resolved light contribution for one logical canvas layer.
Properties
shape
Type: LightShape
Positional or directional light shape.
position
Type: Vector3
Position in logical canvas pixels for point lights.
direction
Type: Vector3
Direction in scene space for directional lights.
color
Type: Color
Light color.
intensity
Type: f64
Light intensity.
radius
Type: f64
Point light radius in logical pixels.
enabled
Type: bool
Whether this light contributes.
SceneLighting
Resolved lighting state for one logical canvas layer.
Properties
active
Type: bool
Whether authored lights or ambient overrides are present.
ambient_is_authored
Type: bool
Whether ambient came from an enabled authored AmbientLight.
The default ambient is only applied to primitives with at least one eligible direct light. An authored ambient remains layer-wide.
ambient
Type: SceneAmbientLight
Singleton ambient contribution.
lights
Type: Vec<SceneLight>
Positional and directional light contributions.
Implementations
DEFAULT_AMBIENT_INTENSITY
Ambient intensity used when point/directional lights exist but no explicit ambient override exists.
MAX_LIGHTS
Maximum number of simultaneously enabled lights in one target canvas layer.
identity
pub fn identity() -> Self
Returns lighting that preserves unlit legacy rendering.
with_default_ambient
pub fn with_default_ambient(lights: Vec<SceneLight>) -> Self
Creates active lighting with Pax's default ambient term.
Stroke
Describes the outline drawn around vector geometry.
Pax currently renders strokes centered on the underlying path. For open
geometry, cap controls how the stroke terminates at the start and end of
the path, while join controls how adjacent segments meet.
Properties
color
The stroke color, including alpha.
width
The stroke width.
The type is [Size] for consistency with the wider property system, but
current vector renderers interpret this value in pixels.
cap
The cap style used for exposed endpoints on open paths.
join
Type: Property<StrokeJoin>
The join style used where adjacent stroke segments meet.
Vector3
A three-dimensional vector in logical scene space.
Properties
x
Type: f64
X component.
y
Type: f64
Y component.
z
Type: f64
Z component.
Implementations
new
pub fn new(x: f64, y: f64, z: f64) -> Self
Constructs a 3D vector.
Enums
Fill
Describes how to fill vector geometry.
Variants
Solid(Color)
A single solid color.
LinearGradient(LinearGradient)
A linear gradient.
RadialGradient(RadialGradient)
A radial gradient.
Implementations
coverage_alpha_0_1
pub fn coverage_alpha_0_1(&self) -> f64
Estimates the alpha coverage contributed by this fill.
linearGradient
pub fn linearGradient(start: (Size, Size), end: (Size, Size), stops: Vec<GradientStop>) -> Fill
Constructs a linear gradient fill.
Pax templates should normally use @gradient. When this helper is
needed explicitly, pass start and end as [x, y] lists.
max_alpha_0_1
pub fn max_alpha_0_1(&self) -> f64
Returns the maximum alpha used by this fill.
with_alpha_factor
pub fn with_alpha_factor(&self, factor: f64) -> Fill
Returns a copy of this fill with alpha multiplied by factor.
LightShape
Shape of a light contribution in logical scene space.
Variants
Point
A positional light with radius-based attenuation.
Directional
A light with direction but no position or attenuation.
Material
Light-reactive surface response for vector primitives.
This is intentionally named Material; texture is reserved for future
bitmap-backed texture maps and pattern data.
Variants
Lit(MaterialParams)
Responds to scene lighting using parameterized material coefficients.
Unlit
Ignores scene lighting and preserves legacy unlit rendering behavior.
Implementations
custom
pub fn custom(params: MaterialParams) -> Self
Creates a lit material from explicit coefficients.
emissive
pub fn emissive(color: Color, intensity: f64) -> Self
An emissive material that adds color independent of lights.
glossy
pub fn glossy(specular: f64) -> Self
A higher-specular material with lower roughness.
matte
pub fn matte() -> Self
A soft, low-specular material suitable as the default lit response.
metallic
pub fn metallic(metallic: f64) -> Self
A metallic material response.
unlit
pub fn unlit() -> Self
A material that ignores authored lights.
NavigationTarget
Describes where to open new windows or tabs when navigating to a URL from a Link node.
Variants
Current
Navigate in the current window or tab.
New
Navigate in a new window or tab.
PathElement
Describes a single element of a vector path, such as a line, a point, or curve segment.
Variants
Empty
No-op path element.
Point(Size, Size)
Moves the current point to the provided coordinate.
Line
Draws a straight line to the following Point.
Quadratic(Size, Size)
Draws a quadratic Bézier segment with one control point, ending at the following Point.
Cubic(Size, Size, Size, Size)
Draws a cubic Bézier segment with two control points, ending at the following Point.
Close
Closes the current contour.
PathSmoothing
Controls optional curve smoothing for path geometry before tessellation.
PathSmoothing is intended for authored or imported paths whose source data
approximates curves with many short line segments, such as single-stroke SVG
fonts. Existing paths keep their exact geometry by default.
Variants
None
Preserve the authored path exactly.
Light
Lightly smooth polyline runs while preserving sharp corners.
Strong
More aggressively smooth polyline runs for pen-like paths.
StrokeCap
Controls how an open stroke terminates at the exposed endpoints of a path.
StrokeCap affects primitives such as Line and open Path subpaths.
Closed geometry ignores cap style because it has no exposed endpoints.
Variants
Butt
Ends exactly at the path endpoint without extending past it.
Round
Adds a semicircular cap whose radius is half the stroke width.
Square
Adds a square cap that extends half the stroke width past the endpoint.
StrokeJoin
Controls how stroke segments are joined at path vertices.
Variants
Miter
Extends outer edges to a point, subject to the renderer's miter limit.
Round
Rounds the outside of each join.
Bevel
Cuts joins off with a straight edge.
drawing::path_smoothing
Functions
smooth_bez_path
pub fn smooth_bez_path(path: &BezPath, smoothing: PathSmoothing) -> BezPath
Converts long polyline runs in path to cubic Bezier segments.
Existing quadratic and cubic segments are preserved. Sharp corners split a line run so smoothing does not round deliberate angular forms.
drawing::path_trim
Functions
trim_bez_path
pub fn trim_bez_path(path: &BezPath, draw_start: f64, draw_end: f64) -> BezPath
Returns the sub-path visible between normalized draw_start and draw_end.
drawing::stroke_utils
Functions
stroke_width_pixels
pub fn stroke_width_pixels(stroke: &Stroke) -> f64
Resolves a stroke width as pixels.
stroked_outline_path
pub fn stroked_outline_path(centerline: &BezPath, stroke: &Stroke) -> Option<BezPath>
Builds the filled outline path corresponding to a stroked centerline path.
This is useful for hit testing, masking, and occlusion, where stroke coverage needs to be treated as fill geometry.
events
Event payloads and cancellation wrappers passed to Pax event handlers.
Structs
ButtonClick
User activates a native button by click or tap.
CheckboxChange
User checks or unchecks a checkbox.
Properties
checked
Type: bool
The new checked state.
Click
User activates an element with a mouse click or single-touch tap.
@click and @tap handlers both receive Event<Click>. A touch tap is
emitted after touch end and normalized with button set to
MouseButton::Left and no modifiers.
Properties
mouse
Type: MouseEventArgs
Common mouse event data.
ContextMenu
User right-clicks an element to open the context menu.
Properties
mouse
Type: MouseEventArgs
Common mouse event data.
DoubleClick
User double-clicks a mouse button over an element.
Properties
mouse
Type: MouseEventArgs
Common mouse event data.
Drop
User drops a file-like payload over an element.
Properties
x
Type: f64
The x-coordinate of the drop in the receiving node's local coordinate space.
y
Type: f64
The y-coordinate of the drop in the receiving node's local coordinate space.
name
Type: String
File or payload name supplied by the platform.
mime_type
Type: String
MIME type supplied by the platform.
data
Type: Vec<u8>
Raw dropped payload bytes.
Event
Event wrapper passed to Pax event handlers.
Event<T> carries the typed event payload and shared cancellation state,
allowing handlers to call prevent_default where the active chassis supports it.
Properties
args
Type: T
The event-specific payload, for example Click or KeyDown.
Implementations
new
pub fn new(args: T) -> Self
Constructs an event wrapper around a raw payload.
prevent_default
pub fn prevent_default(&self)
Cancels the platform default for this event, when the chassis supports cancellation.
Focus
Window or component focused.
KeyDown
User is pressing a key.
Properties
keyboard
Type: KeyboardEventArgs
Common keyboard event data.
KeyPress
User presses a key that displays a character (alphanumeric or symbol).
Properties
keyboard
Type: KeyboardEventArgs
Common keyboard event data.
KeyUp
User has released a key.
Properties
keyboard
Type: KeyboardEventArgs
Common keyboard event data.
KeyboardEventArgs
Common properties in keyboard events.
Properties
key
Type: String
Platform-normalized key string.
modifiers
Type: Vec<ModifierKey>
Modifier keys active during the event.
is_repeat
Type: bool
Whether this event was generated by key-repeat.
MouseDown
User presses a mouse button over an element.
Properties
mouse
Type: MouseEventArgs
Common mouse event data.
MouseEventArgs
Common properties in mouse-backed events and normalized activation events.
Properties
x
Type: f64
The window-space x-coordinate of the event.
Use NodeContext::local_point to convert it into coordinates relative to a handler's node.
y
Type: f64
The window-space y-coordinate of the event.
Use NodeContext::local_point to convert it into coordinates relative to a handler's node.
button
Type: MouseButton
Mouse button associated with the event.
modifiers
Type: Vec<ModifierKey>
Modifier keys active during the event.
MouseMove
User moves the mouse while it is over an element.
Properties
mouse
Type: MouseEventArgs
Common mouse event data.
MouseOut
User moves the mouse away from an element.
MouseOver
User moves the mouse onto an element.
MouseUp
User releases a mouse button over an element.
Properties
mouse
Type: MouseEventArgs
Common mouse event data.
PhotoPickerChange
Native photo picker completion event.
Properties
id
Type: u32
Picker node id that produced the result.
request_id
Type: u64
App-controlled request id, usually copied from the picker trigger value.
status
Type: PhotoPickerStatus
Completion status.
message
Type: Option<String>
Human-readable platform message for errors or partial results.
photos
Type: Vec<PhotoPickerPhoto>
Selected photos, empty for cancellation, permission denial, or unavailable sources.
PhotoPickerPhoto
One selected image returned by a native photo picker.
Properties
temp_id
Type: String
Stable temporary identifier for this selection.
file_name
Type: Option<String>
File name supplied by the platform, when available.
mime_type
Type: String
MIME type supplied or inferred by the platform.
byte_size
Type: u64
Byte size of the selected asset.
width
Type: Option<u32>
Image width in pixels, when available.
height
Type: Option<u32>
Image height in pixels, when available.
source_kind
Type: PhotoPickerSourceKind
Source used for this image.
handle
Type: Option<String>
Platform preview/read handle, such as an object URL or file URL.
data
Type: Option<Vec<u8>>
Copied asset bytes when the chassis can provide them within configured limits.
Scroll
Scroll is the shared delta-based path for wheel scrolling, touch swipe gestures, and
read-only native scroller position changes.
Use Wheel or TouchMove when you need platform-specific details instead of the normalized
delta stream.
Properties
delta_x
Type: f64
Horizontal scroll delta.
delta_y
Type: f64
Vertical scroll delta.
SelectStart
A native text selection is beginning.
SliderChange
User changes a slider's value.
Properties
value
Type: f64
The new slider value.
TextInput
User types into a text input field.
Properties
text
Type: String
Text committed by the input event.
TextboxChange
User changes the text in a text input field.
Properties
text
Type: String
Current text after the change.
TextboxInput
Text input reported while the user edits a textbox.
On web, this follows the DOM input event. TextboxChange follows the
DOM change event instead; commit timing depends on the platform control.
Properties
text
Type: String
Current text reported by the input event.
Touch
Represents a single touch event.
Properties
x
Type: f64
The window-space x-coordinate of this touch point.
Use NodeContext::local_point to convert it into coordinates relative to a handler's node.
y
Type: f64
The window-space y-coordinate of this touch point.
Use NodeContext::local_point to convert it into coordinates relative to a handler's node.
identifier
Type: i64
Stable identifier for this touch across a touch sequence.
delta_x
Type: f64
Horizontal delta since the previous touch update.
delta_y
Type: f64
Vertical delta since the previous touch update.
TouchCancel
A TouchCancel occurs when the platform aborts an active touch sequence before normal release, such as during a system interruption or host-level gesture cancellation.
The contained touches represent the cancelled touch points and the event targets, then
releases, the node captured at touch start. Cancelled touches must not produce activation.
A native Scroller that can observe its pan simultaneously with child touches keeps delivering
move events and finishes the child sequence with TouchEnd; winning scroll arbitration alone
does not cancel the touch.
Properties
touches
Type: Vec<Touch>
Touch points whose active sequence was cancelled.
TouchEnd
A TouchEnd occurs when the user stops touching an element.
The contained touches represent a list of touch points and the event targets, then releases,
the node captured at touch start.
Properties
touches
Type: Vec<Touch>
Touch points that ended.
TouchMove
A TouchMove occurs when the user moves while touching an element.
The contained touches represent a list of touch points and the event targets the node captured
at touch start, even when the point has moved outside its bounds.
Properties
touches
Type: Vec<Touch>
Active touch points after this touch movement.
TouchStart
A TouchStart occurs when the user touches an element.
The contained touches represent a list of touch points. The hit node is captured for the
touch identifier so subsequent move and end events keep routing to the same subtree.
Properties
touches
Type: Vec<Touch>
Active touch points at the start of this touch sequence.
Wheel
User scrolls the mouse wheel over an element.
Properties
x
Type: f64
The x-coordinate of the wheel event in the receiving node's local coordinate space.
y
Type: f64
The y-coordinate of the wheel event in the receiving node's local coordinate space.
delta_x
Type: f64
Horizontal wheel delta.
delta_y
Type: f64
Vertical wheel delta.
modifiers
Type: Vec<ModifierKey>
Modifier keys active during the event.
Enums
ModifierKey
The variants of modifier keys available on keyboard-supporting platforms.
Variants
Shift
Shift key.
Control
Control key.
Alt
Alt or Option key.
Command
Command, Meta, or Windows key.
MouseButton
The variants of mouse buttons available on mouse-supporting platforms.
Variants
Left
Primary mouse button.
Right
Secondary mouse button.
Middle
Middle mouse button.
Unknown
Button not identified by the chassis.
PhotoPickerSourceKind
Platform source used to produce a selected photo.
Variants
Library
Existing image from a photo library.
File
Existing image from the filesystem.
Camera
Newly captured camera image.
Other(String)
Source not recognized by this runtime version.
PhotoPickerStatus
Completion status for a native photo picker request.
Variants
Selected
One or more photos were selected.
Cancelled
The platform picker was dismissed without a selection.
PermissionDenied
Camera or picker permission was denied or restricted.
Unavailable
The requested source is not available on this platform/device.
SizeLimitExceeded
One or more candidate photos exceeded the configured byte limit.
Failed
The platform picker failed for another reason.
layout
Layout units, axes, and common properties shared by renderable nodes.
Structs
CommonProperties
Properties shared by every renderable Pax node.
Each property here is special-cased by the compiler when parsing element properties,
for example <SomeElement width={...} />.
Properties
id
Type: Property<Option<String>>
Optional stable node identifier.
x
Horizontal position.
y
Vertical position.
padding_x
Symmetric inner spacing applied to this node's child layout area on the x axis.
padding_y
Symmetric inner spacing applied to this node's child layout area on the y axis.
width
Horizontal extent.
height
Vertical extent.
anchor_x
Horizontal transform origin, relative to the node's own bounds.
anchor_y
Vertical transform origin, relative to the node's own bounds.
scale_x
Horizontal scale coefficient.
scale_y
Vertical scale coefficient.
skew_x
Type: Property<Option<Rotation>>
Horizontal skew.
skew_y
Type: Property<Option<Rotation>>
Vertical skew.
rotate
Type: Property<Option<Rotation>>
Rotation around the z axis.
transform
Type: Property<Option<Transform2D>>
Full composed transform.
opacity
Type: Property<Option<Opacity>>
Node opacity, applied to the node and its descendants.
layout_role
Type: Property<Option<LayoutRole>>
Controls whether this node participates in parent layout measurement and flow.
unclippable
Type: Property<Option<bool>>
Allows a node to render outside an ancestor clipping frame.
Enums
Axis
Model of 2D cartesian axes, used to disambiguate calculations that depend on axis direction (e.g. width vs height)
Variants
X
Horizontal axis.
Y
Vertical axis.
LayoutRole
Controls whether a node participates in parent layout measurement.
Variants
Default
Normal layout behavior. The node contributes to parent hulls, autosize, and flow.
Breakout
Parent-local positioning that does not contribute to parent hulls, autosize, or flow.
Breakout remains in the normal render, hit-test, scroll, and clipping trees; it does
not portal above ancestor frames, masks, or scrollers.
Size
A spatial size value that can be either a concrete pixel value like 25px, a percent of parent bounds like 50%,
or an additive/subtractive combination of the two like (100% - 10px).
Variants
Pixels(Numeric)
Concrete pixel length, such as 25px.
Percent(Numeric)
Percent length relative to the relevant parent bound, such as 50%.
Combined(Numeric, Numeric)
Additive pixel and percent components, such as 100% - 10px.
Implementations
ZERO
pub fn ZERO() -> Self
Returns a zero-pixel size.
expect_percent
pub fn expect_percent(&self) -> f64
Returns the wrapped percent value normalized as a float, such that 100% => 1.0. Panics if wrapped type is not a percentage.
expect_pixels
pub fn expect_pixels(&self) -> Numeric
Returns the pixel value Panics if wrapped type is not pixels.
evaluate
pub fn evaluate(&self, bounds: (f64, f64), axis: Axis) -> f64
Evaluate a Size in the context of bounds and a target axis.
Returns a Pixel value as a simple f64; calculates Percent with respect to bounds & axis
get_pixels
pub fn get_pixels(&self, parent: f64) -> f64
Resolves this size against a parent extent in pixels.
math
Structs
Generic
Generic, untyped coordinate space.
Traits
Space
Marker trait for phantom coordinate spaces.
pax_value
Submodules
Enums
PaxAny
This type serves a similar purpose as Box<dyn Any>, but allows for special
handling of some types, enabling things like coercion.
Variants
Builtin(PaxValue)
Built-in PAXEL/runtime value.
Any(Box<dyn Any>)
Arbitrary Rust value boxed for runtime storage.
PaxValue
Runtime container for polymorphic values, for evaluating PAXEL.
Two important traits are related to this type: ToFromPaxValue - responsible for converting to and from specific types (u8, String, Color, etc) CoercionRules - responsible for coercing a PaxValue to a specific type (possibly from multiple different variants)
Variants
Bool(bool)
Boolean value.
Numeric(Numeric)
Polymorphic numeric value.
String(String)
UTF-8 string value.
Size(Size)
Pax size value, such as 25px or 50%.
Percent(Percent)
Raw percent value.
Color(Box<Color>)
Pax color value.
Rotation(Rotation)
Pax rotation value.
Duration(Duration)
Pax animation duration value, such as 250ms, 1s, or 10f.
PathElement(Box<PathElement>)
Vector path element value.
Option(Box<Option<PaxValue>>)
Optional value.
Vec(Vec<PaxValue>)
Homogeneous or heterogeneous vector value.
Range(Box<PaxValue>, Box<PaxValue>)
Range value.
Object(Vec<(String, PaxValue)>)
Object value represented by named fields.
Enum(Box<(String, String, Vec<PaxValue>)>)
Enum value represented by type name, variant name, and payload values.
Traits
ImplToFromPaxAny
Marker trait for types that can be stored inside PaxAny without a built-in PaxValue representation.
If the type is part of PaxValue, implement CoercionRules instead.
ToFromPaxAny
Trait that marks a type as being representable as a PaxAny, and provides
the implementation for going to/from that type. For all builtins this
means going to/from a pax value. For others to a Box<dyn Any>. This
is automatically Implemented for PaxValue types through the macro
impl_to_from_pax_value!, and for other types by implementing the marker
trait ImplToFromPaxAny.
ToPaxValue
Converts a Rust value into its built-in PaxValue representation.
This is exact conversion, not coercion; coercion is handled separately by
CoercionRules.
Type Aliases
RcPaxValue
Shared runtime PaxValue handle.
pax_value::functions
Structs
Functions
Registry bootstrap for built-in PAXEL helper functions.
Implementations
has_function
pub fn has_function(scope: &str, name: &str) -> bool
Returns true if a helper function exists in the named scope.
register_all_functions
pub fn register_all_functions()
Registers built-in math, color, and transform helper functions.
Traits
HelperFunctions
Registers helper functions made available to PAXEL scopes.
pax_value::numeric
Enums
Numeric
Numeric type wrapper for PAXEL and runtime polymorphic numeric operations. This broad polyfill specifically enables terse numeric operations within expressions, without the overhead of explicit typing or numeric microsyntax.
Variants
I8(i8)
I16(i16)
I32(i32)
I64(i64)
U8(u8)
U16(u16)
U32(u32)
U64(u64)
F64(f64)
F32(f32)
ISize(isize)
USize(usize)
Implementations
is_float
pub fn is_float(&self) -> bool
Returns true when this value is stored as a floating-point number.
max
pub fn max(self, other: Self) -> Self
Returns the larger of two numeric values.
min
pub fn min(self, other: Self) -> Self
Returns the smaller of two numeric values.
pow
pub fn pow(self, exp: Self) -> Self
Raises this number to exp, preserving integer arithmetic when both sides are integral.
to_float
pub fn to_float(&self) -> f64
Coerces this numeric value to f64.
to_int
pub fn to_int(&self) -> i64
Coerces this numeric value to i64.
platform
Platform, viewport, and coordinate-space marker types.
Structs
Accel
Current device acceleration, in meters per second squared.
On web targets, this uses DeviceMotionEvent.accelerationIncludingGravity
when available, falling back to DeviceMotionEvent.acceleration.
Properties
x
Type: f64
Acceleration along the x axis.
y
Type: f64
Acceleration along the y axis.
z
Type: f64
Acceleration along the z axis.
Gyro
Current device orientation reported by a gyroscope/orientation sensor.
On web targets, this is sourced from DeviceOrientationEvent and mapped as:
x = beta, y = gamma, and z = alpha, all in degrees.
Properties
x
Type: f64
Front-to-back tilt, in degrees.
y
Type: f64
Left-to-right tilt, in degrees.
z
Type: f64
Compass/z-axis rotation, in degrees.
NativeLiquidGlassScope
Runtime-inherited Apple liquid-glass effect scope.
Properties
group_id
Type: u32
Stable node id for the nearest liquid-glass scope.
spacing
Type: f64
Desired spacing between grouped glass surfaces, in pixels.
interactive
Type: bool
Whether supported Apple surfaces should use the interactive glass effect.
tint
Type: Option<Color>
Optional tint for supported Apple surfaces.
variant
Type: String
Apple glass style name, currently "regular" or "clear".
Implementations
to_message
pub fn to_message(&self) -> AppleLiquidGlassPatch
Converts runtime style data into the serialized native-message payload.
TargetInfo
Derived target facts exposed to PAXEL and Rust event handlers.
Properties
web
Type: bool
Browser-hosted rendering target.
native
Type: bool
Native application rendering target.
ios
Type: bool
Any iOS-family target, including iPhone and iPad.
iphone
Type: bool
iPhone-class iOS target.
ipad
Type: bool
iPadOS / iPad-class iOS target.
macos
Type: bool
macOS target.
android
Type: bool
Android target.
windows
Type: bool
Windows target.
linux
Type: bool
Linux target.
mobile
Type: bool
Any mobile OS target.
desktop
Type: bool
Any desktop OS target.
Implementations
new
pub fn new(platform: Platform, os: OS) -> Self
Build target facts from the chassis platform and detected OS.
Viewport
Struct representing the outermost viewport of a rendering scene, for example a browser window or native application window.
Properties
width
Type: f64
Viewport width in pixels.
height
Type: f64
Viewport height in pixels.
major
Type: f64
Larger viewport dimension in pixels.
minor
Type: f64
Smaller viewport dimension in pixels.
aspect
Type: f64
Width divided by height. Returns 0.0 when height is 0.
landscape
Type: bool
True when width is greater than height.
portrait
Type: bool
True when height is greater than width.
square
Type: bool
True when width and height are effectively equal.
Implementations
SQUARE_EPSILON
Equality tolerance used when classifying square viewports.
new
pub fn new(width: f64, height: f64) -> Self
Build viewport facts from width and height in logical pixels.
Window
Phantom coordinate space representing the outer window.
Enums
OS
Describes known operating systems / targets.
Variants
Mac
macOS.
Linux
Linux desktop.
Windows
Windows desktop.
Android
Android.
IPhone
iOS on iPhone-class devices.
IPad
iPadOS / iOS on iPad-class devices.
Unknown
OS has not been detected or reported.
Implementations
is_android
pub fn is_android(&self) -> bool
Returns true for Android.
is_desktop
pub fn is_desktop(&self) -> bool
Helper to determine if the OS is a desktop platform.
is_ios
pub fn is_ios(&self) -> bool
Returns true for either iPhone-class iOS or iPadOS.
is_ipad
pub fn is_ipad(&self) -> bool
Returns true for iPadOS / iPad-class iOS.
is_iphone
pub fn is_iphone(&self) -> bool
Returns true for iPhone-class iOS.
is_linux
pub fn is_linux(&self) -> bool
Returns true for Linux.
is_macos
pub fn is_macos(&self) -> bool
Returns true for macOS.
is_mobile
pub fn is_mobile(&self) -> bool
Helper to determine if the OS is a mobile platform.
is_windows
pub fn is_windows(&self) -> bool
Returns true for Windows.
Platform
Describes categories of known platforms, for differentiating certain engine behaviors.
Variants
Web
Browser-hosted rendering target.
Native
Native application target.
Unknown
Platform unknown or not yet reported.
Implementations
is_native
pub fn is_native(&self) -> bool
Returns true when hosted by a native chassis.
is_web
pub fn is_web(&self) -> bool
Returns true when hosted by the web chassis.
properties
Structs
Property
A reactive value node in Pax's property graph.
Property<T> is the primary state and binding primitive used by generated
components, PAXEL expressions, and Rust component logic.
Implementations
cancel_transitions
pub fn cancel_transitions(&self)
Stops the active transition and clears every queued transition segment.
The property is left at its current eased value. A subsequent [Property::set]
can therefore take immediate ownership without the cancelled transition
overwriting it on the next runtime tick.
computed
pub fn computed(evaluator: impl Fn() -> T + 'static, dependents: &[UntypedProperty]) -> Self
Creates a computed property from an evaluator and dependency list.
computed_with_cutoff
pub fn computed_with_cutoff(evaluator: impl Fn() -> T + 'static, dependents: &[UntypedProperty], cutoff: impl Fn(&T, &T) -> bool + 'static) -> Self
Creates a computed property with a propagation cutoff.
The predicate receives the last accepted value and the newly evaluated
candidate. Returning true discards the candidate and stops outbound
invalidation at this property; returning false accepts and propagates
it. The first evaluation is always accepted.
computed_with_cutoff_and_name
pub fn computed_with_cutoff_and_name(evaluator: impl Fn() -> T + 'static, dependents: &[UntypedProperty], cutoff: impl Fn(&T, &T) -> bool + 'static, name: &str) -> Self
Creates a named cutoff computed property, useful for diagnostics.
computed_with_name
pub fn computed_with_name(evaluator: impl Fn() -> T + 'static, dependents: &[UntypedProperty], name: &str) -> Self
Creates a named computed property, useful for diagnostics.
ease_to
pub fn ease_to<D: Into<Duration>>(&self, end_val: T, duration: D, curve: EasingCurve)
Immediately starts an ease transition from the current value to end_val, over a duration, following curve.
Numeric arguments preserve the historical frame-based behavior. Use
Duration::Milliseconds, Duration::Seconds, or Duration::Frames to
select an explicit unit.
ease_to_later
pub fn ease_to_later<D: Into<Duration>>(&self, end_val: T, duration: D, curve: EasingCurve)
Enqueues an ease transition from the current value to end_val, over a duration, following curve, which will start after all currently enqueued transitions finish.
get
pub fn get(&self) -> T
Gets the currently stored value. Might be computationally expensive in a large reactivity network since this triggers re-evaluation of dirty property chains
new
pub fn new(val: T) -> Self
Creates a literal property with an initial value.
new_with_name
pub fn new_with_name(val: T, name: &str) -> Self
Creates a named literal property, useful for diagnostics.
read
pub fn read<V>(&self, f: impl FnOnce(&T) -> V) -> V
Reads the inner value by reference.
Panics if this property is already borrowed, which can happen if read
is called inside a read of the same property.
replace_with
pub fn replace_with(&self, target: Property<T>)
Replaces this property's evaluator, dependencies, and value with target, while keeping dependents.
This can introduce circular dependencies if used carelessly. It is intended for changing a property from literal to computed (or vice versa) without severing existing outbound links.
set
pub fn set(&self, val: T)
Sets this properties value and sets the dirty bit recursively of all of its dependencies if not already set
set_if_neq
pub fn set_if_neq(&self, val: T) -> bool where T: PartialEq
Sets the value only when it differs from the current one.
Returns true when the write changed the property and dirtied dependents.
untyped
pub fn untyped(&self) -> UntypedProperty
Casts this property to its untyped version.
update
pub fn update(&self, f: impl FnOnce(&mut T))
Get access to a mutable reference to the inner value T. Will trigger updates for dependents of this property, regardless of if the value actually changed
Traits
PropertyValue
Bound for values that can live inside Pax Property<T>.
Values must be cloneable for .get(), interpolatable for transitions, and
'static because properties are stored in the runtime graph.
rendering
Rendering backend contracts and helpers for drawing Pax scene content.
Structs
AlphaMaskPaint
One vector paint contributing alpha to a mask, in local path coordinates. Color channels do not affect coverage; fill alpha and opacity are multiplied.
Properties
path
Type: BezPath
transform
Type: Affine
fill
Type: Fill
opacity
Type: f64
ReplayCanvasLayerUpdate
Replay invalidation for one logical canvas layer.
Properties
layer
Type: usize
node_ids
Type: Option<Vec<u32>>
None means the layer should fall back to region/full-layer dirtification.
Enums
Layer
Render layer selected for a primitive or native surface.
Variants
Native
Platform-native element layer.
NativeNonOccluding
Platform-native layer that does not participate in occlusion.
Canvas
GPU/canvas-rendered layer.
DontCare
Runtime can choose the appropriate layer.
Traits
RenderContext
The Pax render trait, used as a layer of indirection and contract for backend-agnostic rendering.
Functions
bez_path_to_svg_path_data
pub fn bez_path_to_svg_path_data(path: &BezPath) -> String
Convert a kurbo BezPath to a SVG-friendly drawing string
store
Store marker traits for runtime-managed state.
Traits
Store
Marker trait for types that can be inserted into a Pax local store.
Stored objects need to be unique for any given stack. Avoid inserting broad reusable types directly; prefer a local newtype that represents one specific store purpose.
transform
Rotation and 2D transform types used by layout and rendering.
Structs
Transform2D
A sugared representation of an Affine transform combined with an anchor layout property.
Properties
previous
Type: Option<Box<Transform2D>>
Linked list of ancestral Transform2Ds
rotate
Type: Option<Rotation>
Represents affine rotation over z axis (single-dimensional for 2D rendering)
translate
Type: Option<[Size; 2]>
Represents affine translation across the x-y plane
anchor
Type: Option<[Size; 2]>
Represents the alignment of the (0,0) position of this element as it relates to its own bounding box. (origin offset)
scale
Type: Option<[Size; 2]>
Represents affine scale coefficients across the x-y plane
skew
Type: Option<[Rotation; 2]>
Represents affine skew over x and y axes
Implementations
anchor
pub fn anchor(x: Size, y: Size) -> Self
Transform origin point for this element, relative to its own bounding box.
rotate
pub fn rotate(z: Rotation) -> Self
Rotation over the z axis.
scale
pub fn scale(x: Size, y: Size) -> Self
Scale coefficients over the x-y plane.
translate
pub fn translate(x: Size, y: Size) -> Self
Translation over the x-y plane.
Enums
Rotation
Encodes a rotation in various units
Variants
Radians(Numeric)
Radian units (2π rad for one full rotation)
Degrees(Numeric)
Degree units (360 deg for one full rotation)
Percent(Numeric)
Percentage units (100% for one full rotation)
Implementations
ZERO
pub fn ZERO() -> Self
Returns zero degrees.
get_as_degrees
pub fn get_as_degrees(&self) -> f64
Returns the rotation as degrees, regardless of the original unit
get_as_radians
pub fn get_as_radians(&self) -> f64
Returns the rotation as radians, regardless of the original unit
to_float_0_1
pub fn to_float_0_1(&self) -> f64
Returns a normalized float proportional to 0deg : 0.0 :: 360deg : 1.0.
For example, 0rad maps to 0.0, 100% maps to 1.0, and 720deg
maps to 2.0.
unit_value
Normalized unit-domain values.
Enums
UnitValue
A scalar value in a normalized unit domain.
UnitValue accepts both unitless numbers and percents. It is useful for
properties whose authoring domain is "part of a whole", such as path drawing
progress where 0.5 and 50% describe the same position.
Variants
Unitless(Numeric)
Unitless normalized value, where 0.5 means halfway through the domain.
Percent(Numeric)
Percent value, where 50% means halfway through the domain.
Implementations
to_clamped_unit_float
pub fn to_clamped_unit_float(&self) -> f64
Returns to_unit_float() clamped into the closed unit interval.
to_unit_float
pub fn to_unit_float(&self) -> f64
Returns the wrapped value as a normalized unitless float.
UnitValue::Unitless(0.5) returns 0.5, and
UnitValue::Percent(50) returns 0.5.
variables
Property adapters that expose runtime values to expression scopes.
Structs
Variable
Runtime property adapter used by expression scopes.
Implementations
get_as_pax_value
pub fn get_as_pax_value(&self) -> PaxValue
Reads the current value as a PaxValue.
new
pub fn new<T: PropertyValue + ToPaxValue>(untyped_property: UntypedProperty) -> Self
Wraps an untyped property and exposes it as a PaxValue.
new_from_typed_property
pub fn new_from_typed_property<T: PropertyValue + ToPaxValue>(property: Property<T>) -> Self
Wraps a typed property and exposes it as a PaxValue.
read_pax_value_ref
pub fn read_pax_value_ref<V>(&self, f: impl FnOnce(&PaxValue) -> V) -> V
Reads the current PaxValue by reference.
try_typed_binding
pub fn try_typed_binding<T: PropertyValue + CoercionRules>(&self, name: &str) -> Option<Property<T>>
Creates an independent one-way binding when exact typed forwarding is
safe. Unlike a double binding, writes/easing on the result never mutate
the source. Mismatched types and custom conversions return None.
pax-std
Submodules
core
Submodules
- core::event_blocker
- core::frame
- core::group
- core::import_settings
- core::link
- core::liquid_glass
- core::mask
- core::router
- core::scroller
- core::text
core::event_blocker
Structs
EventBlocker
Native surface that absorbs pointer events before they reach content beneath it.
background defaults to transparent. A solid background is useful for modal
underlays that must composite above native controls and scroller canvas islands.
Properties
background
Solid background painted by the native surface.
core::frame
Structs
Frame
A primitive that gathers children underneath a single render node with a shared base transform,
like Group, except Frame has the option of clipping rendering outside
of its bounds.
If clipping or the option of clipping is not required,
a Group will generally be a more performant and otherwise-equivalent
to Frame, since Frame creates a clipping mask.
Properties
autosize
Type: Property<bool>
Automatically sizes the frame to its direct content children when possible.
autosize_x
Type: Property<Option<bool>>
Optional override for whether autosize manages the x axis.
autosize_y
Type: Property<Option<bool>>
Optional override for whether autosize manages the y axis.
corner_radius
Type: Property<f64>
Corner radius used for the frame clipping mask, in pixels.
core::group
Structs
Group
Gathers a set of children underneath a single render node: useful for composing transforms and simplifying render trees.
Properties
autosize
Type: Property<bool>
Automatically sizes the group to its direct content children when possible.
autosize_x
Type: Property<Option<bool>>
Optional override for whether autosize manages the x axis.
autosize_y
Type: Property<Option<bool>>
Optional override for whether autosize manages the y axis.
corner_radius
Type: Property<f64>
Corner radius used when the group materializes a native surface, in pixels.
core::import_settings
Structs
ImportSettings
Mounts a non-rendering subtree whose component descendants export selector settings.
core::link
Structs
Link
Navigates to a URL when its slotted content is clicked or tapped.
Link remains router-agnostic: it writes a URL, while Router and Route
declaratively read the current location. On web targets, same-origin
target=Current navigation can be serviced through client-side history
updates instead of a full document reload.
Properties
url
Type: Property<String>
Destination URL.
target
Whether to open the URL in the current or a new browsing context.
autosize
Type: Property<bool>
Automatically sizes the link wrapper to its slotted content when possible.
autosize_x
Type: Property<Option<bool>>
Optional override for whether autosize manages the x axis.
autosize_y
Type: Property<Option<bool>>
Optional override for whether autosize manages the y axis.
Enums
Target
Navigation target for Link.
Variants
Current
Navigate in the current window or tab.
New
Navigate in a new window or tab.
core::liquid_glass
Structs
LiquidGlass
Applies an Apple liquid-glass native effect to supported descendant native surfaces.
Properties
enabled
Type: Property<bool>
Whether this liquid-glass scope is active. When false, descendants opt out.
spacing
Desired spacing between grouped glass surfaces, in Pax units.
interactive
Type: Property<bool>
Whether supported Apple glass surfaces should use the interactive effect.
tint
Optional tint for supported Apple glass surfaces.
variant
Type: Property<String>
Apple glass style. Supported values are currently "regular" and "clear".
core::mask
Structs
Mask
Clips its first child using its second child subtree, which is not rendered as
visible content. By default, this is a geometric coverage mask. Set alpha=true
to use painted alpha instead, and feather to soften its edge:
<Mask width=100% height=100% alpha=true feather=12.0>
<Rectangle width=100% height=100% fill=#FF0088/>
<Ellipse x=50% y=50% anchor=50% width=240px height=240px
fill=TRANSPARENT stroke={color: WHITE, width: 40px}/>
</Mask>
The example reveals pink only underneath the soft ring. feather is Gaussian
standard deviation in logical pixels, independent of display pixel density;
zero disables feathering. Both properties default to zero/false, preserving
existing coverage-mask behavior.
Alpha sources support Rectangle, Ellipse, and Path fills and strokes,
including source-relative opacity, transforms, and linear/radial gradient alpha
(up to eight ordered stops). Source RGB does not matter. Grouping and keyed
for loops combine paints using source-over alpha: overlapping half-opacity
sources yield 75% coverage, not XOR. Nested alpha masks on content multiply;
ordinary geometric clips continue to intersect them. An empty alpha source
hides all content. Cached surface-sized GPU textures are reused until paint,
feather, enclosing alpha, or surface dimensions change.
Current boundary: WGPU canvas rendering, verified on web. Native controls and
the legacy Piet renderer do not support alpha masks. Source-side Frame/Mask
clipping, images, text, and native elements are not alpha sources; use vector
leaves inside Group/repeat containers. Alpha masks modulate canvas draw alpha,
not an isolated offscreen group, and do not change hit testing. Keep interactive
hit targets separate from purely visual alpha reveals.
Properties
alpha
Type: Property<bool>
Use painted alpha instead of geometric coverage.
feather
Type: Property<f64>
Gaussian feather standard deviation, in logical pixels, for alpha masks.
core::router
Structs
Route
Default route branch shell consumed by a parent [Router].
Use path for explicit path matching, :param for single-segment capture,
and a terminal * to consume the remaining tail. Use default=true to
provide the fallback branch when no explicit path matches.
Properties
path
Type: Property<String>
Static route pattern, e.g. /docs/:slug or /settings/*.
Patterns are matched against the current router scope, not always the full global path. At the root router, that scope is the full location.
default
Type: Property<bool>
Fallback branch used when no path branch matches.
RouteCard
Declarative route branch presented as a card over the current route.
RouteCard is consumed by a parent [Router] like [Route], but the
presentation behavior is owned by this component shell. It retains the
previously mounted branch underneath so the incoming or outgoing card slides
over stable content while a black scrim fades over the retained branch. The
route's own contents may still declare additional element or component
lifecycle transitions.
Properties
path
Type: Property<String>
Static route pattern, e.g. /details/:id.
default
Type: Property<bool>
Fallback card branch used when no path branch matches.
edge
Type: Property<RouteCardEdge>
Edge used by the generated card enter/exit transition.
duration
Duration for the generated card enter/exit transition.
scrim_opacity
Maximum opacity for the black scrim over the retained background.
Unitless values are normalized alpha (0.3 is 30%); percentages such
as 30% are also supported.
curve
Type: Property<RouteCardCurve>
Reserved easing tuning for the card enter/exit transition.
RouteModal
Stacked route branch consumed by a parent [Router].
RouteModal matches like [Route], but it keeps the previously mounted
branch active underneath while the modal branch is active. The shell fades a
black scrim over the retained branch; the route's own contents are
responsible for any modal-specific enter/exit transition.
Properties
path
Type: Property<String>
Static route pattern, e.g. /tools.
default
Type: Property<bool>
Fallback modal branch used when no path branch matches.
duration
Duration for the generated modal scrim enter/exit transition.
scrim_opacity
Maximum opacity for the black scrim over the retained background.
Unitless values are normalized alpha (0.3 is 30%); percentages such
as 30% are also supported.
Router
Declarative route reader that selects one active [Route] child subtree.
Router is a control-flow primitive: it does not render by itself.
Instead, it matches the current location against its child Route branches
and mounts only the winning subtree.
The active subtree receives an implicit route binding with:
route.location: the location scoped to this routerroute.global_location: the full browser/native locationroute.params: named captures from:paramsegmentsroute.remainder: the unmatched tail after this branchroute.is_exactandroute.consumed_segments: match metadata
Nested routers match against the nearest ancestor route.remainder by
default, which keeps route trees composable without manual string slicing.
Enums
RouteCardCurve
Reserved easing curve values for [RouteCard] transition tuning.
Variants
Linear
Hold
InQuad
OutQuad
InOutQuad
InBack
OutBack
InOutBack
RouteCardEdge
Edge used by [RouteCard] transitions.
Variants
Leading
Trailing
Top
Bottom
core::scroller
Structs
Scroller
A scrolling container, which clips its bounds and offers platform-native scrolling on either or both of the horizontal and vertical axes.
Properties
scroll_pos_x
Type: Property<f64>
Horizontal scroll offset, in pixels.
scroll_pos_y
Type: Property<f64>
Vertical scroll offset, in pixels.
scroll_width
Width of the scrollable content pane.
scroll_height
Height of the scrollable content pane.
autosize
Type: Property<bool>
Automatically sizes the scroll pane on its default axes when possible.
For Scroller, the default autosized axis is y; x remains bound to
scroll_width unless autosize_x explicitly opts in.
autosize_x
Type: Property<Option<bool>>
Optional override for whether autosize manages the x axis.
autosize_y
Type: Property<Option<bool>>
Optional override for whether autosize manages the y axis.
corner_radius
Type: Property<f64>
Corner radius for the scroller clipping region, in pixels.
snap_positions_x
Scroll snap anchors expressed in px/% along each axis. Web maps to CSS scroll-snap-type + scroll-snap-align; Apple chassis map these offsets to native scroll end-points while keeping engine scroll state authoritative.
snap_positions_y
Vertical scroll snap anchors expressed in px/%.
core::text
Structs
Text
Renders and styles text through the target's native text system.
A constrained width with an omitted height allows native measurement to determine the height of wrapped content. Selection, editing, font loading, and accessibility behavior depend on the target's native implementation; test the intended interaction on each shipping target.
Properties
editable
Type: Property<bool>
Whether the text can be edited by the user.
selectable
Type: Property<bool>
Requests selectable text. On the current iOS/iPadOS path, non-editable
text uses the interactive selection view only when clip is enabled.
clip
Type: Property<bool>
Whether text overflow is clipped to the node bounds.
text
Type: Property<String>
Text content to display.
style
Text styling.
markdown
Type: Property<bool>
Whether text should be interpreted as Markdown.
wrap
Type: Property<bool>
Whether long text lines should wrap inside the node bounds.
TextStyle
Struct describing platform-agnostic text display properties.
Properties
font
Font family/source/style/weight configuration.
font_size
Font size, in pixels.
fill
Text color. Native text patches reduce gradient fills to their first stop's color; use a solid fill for predictable text color.
underline
Type: Property<bool>
Whether text should be underlined.
align_multiline
Type: Property<TextAlignHorizontal>
Alignment for multiline text layout.
align_vertical
Type: Property<TextAlignVertical>
Vertical text alignment within its bounds.
align_horizontal
Type: Property<TextAlignHorizontal>
Horizontal text alignment within its bounds.
Enums
Font
Describes a font available to native text renderers.
Pax templates may use either the explicit [Font::Web] constructor or a
contextual shorthand. A string names a locally available family with an
empty source URL and normal style and weight:
font: "Times New Roman"
Named object fields describe font sources and optional modifiers. Omitted
fields retain their defaults. Supplying family without url selects a
locally available family, matching the string shorthand. weight accepts
either [FontWeight] or its CSS numeric equivalent from 100 through
900:
font: {
family: "Inter"
url: "https://example.com/Inter-Italic.ttf"
style: FontStyle::Italic
weight: 700
}
Nonempty URLs identify font files, except that Google Fonts URLs containing
fonts.googleapis.com/css receive special stylesheet handling. Relative
asset URLs work on web; the native Web-font loader does not resolve them
into application bundle resources. Native targets also require the actual
font family name, rather than a browser-only alias.
Font shorthand has no positional ("magic index") fields: named keys are
canonical, and positional list/tuple forms are not accepted. Use the
explicit [Font::Web] constructor as verbose longhand when needed.
Variants
Web(String, String, FontStyle, FontWeight)
Font described by family name, source URL, style, and weight.
FontStyle
Describes available font styles.
Variants
Normal
Italic
Oblique
FontWeight
Describes available font weights.
Variants
Thin
ExtraLight
Light
Normal
Medium
SemiBold
Bold
ExtraBold
Black
Implementations
decrease
pub fn decrease(weight: FontWeight) -> FontWeight
Returns the next lighter named font weight.
increase
pub fn increase(weight: FontWeight) -> FontWeight
Returns the next heavier named font weight.
TextAlignHorizontal
Describes available horizontal text alignments.
Variants
Left
Center
Right
TextAlignVertical
Describes available vertical text alignments.
Variants
Top
Center
Bottom
drawing
Submodules
- drawing::ellipse
- drawing::handwriter
- drawing::lighting
- drawing::line
- drawing::path
- drawing::rectangle
drawing::ellipse
Structs
Ellipse
A 2D vector ellipse, which inscribes its bounding box with the specified fill and stroke.
Properties
stroke
Stroke drawn around the ellipse.
fill
Fill painted inside the ellipse.
material
Light-reactive surface response.
drawing::handwriter
Structs
Handwriter
Renders text as single-stroke vector paths suitable for handwriting effects.
Handwriter uses bundled SVG stroke fonts and translates text into
PathElement data for an inner Path. Use draw_start and draw_end to
animate the visible writing range.
Properties
text
Type: Property<String>
Text to render. Newline characters create additional baselines.
font
Type: Property<HandwriterFont>
Bundled stroke font used to draw text.
stroke
Stroke used for the generated path.
smoothing
Type: Property<PathSmoothing>
Optional curve smoothing applied to generated path geometry.
alt_text
Type: Property<String>
Accessible text label. When empty, text is used.
selectable
Type: Property<bool>
Whether the invisible native text layer can be selected.
draw_start
Start position of the visible handwriting range.
draw_end
End position of the visible handwriting range.
line_height
Type: Property<f64>
Baseline-to-baseline multiplier relative to the font's em size.
Enums
HandwriterFont
Bundled single-stroke fonts available to Handwriter.
Variants
EMSAllure
Cursive, airy script.
EMSDelight
Friendly rounded print hand.
EMSInvite
Tall invitation script.
EMSLeague
Flowing connected script.
EMSNeato
Casual narrow script.
EMSOsmotron
Rectilinear plotter hand.
EMSReadability
Highly readable manuscript hand.
EMSTech
Technical drafting hand.
HersheySans1
Classic Hershey sans stroke font.
HersheyScript1
Classic Hershey connected script.
drawing::lighting
Structs
AmbientLight
A non-rendering singleton ambient-light override for its scene.
Properties
color
Ambient color.
intensity
Type: Property<f64>
Ambient intensity multiplier.
enabled
Type: Property<bool>
Whether this ambient override participates in topmost-wins selection.
LightFrame
A non-rendering container that keeps descendant lights from escaping its subtree.
Lights outside the frame may still illuminate its descendants. Each expanded frame instance has its own lexical lighting identity.
LightSource
A non-rendering light resource that affects light-reactive vector materials in its scene.
Properties
color
Light color.
intensity
Type: Property<f64>
Light intensity multiplier.
radius
Radius for point-light attenuation.
z
Logical scene depth in pixels.
shape
Type: Property<LightShape>
Positional or directional light shape.
direction
Direction for directional lights.
enabled
Type: Property<bool>
Whether this light contributes to the scene.
drawing::line
Structs
Line
A 2D vector line segment.
x1/y1 and x2/y2 describe the segment endpoints in the primitive's
local coordinate space. The segment is rendered with stroke, whose cap
style controls how the two exposed endpoints terminate.
Properties
x1
The x-coordinate of the start point.
y1
The y-coordinate of the start point.
x2
The x-coordinate of the end point.
y2
The y-coordinate of the end point.
stroke
The stroke used to render the segment.
material
Light-reactive surface response.
drawing::path
Structs
Path
A 2D vector path for arbitrary Bézier and line-segment chains.
elements describes the path in local coordinates. fill paints the
interior of closed contours, while stroke paints the path itself; for
open subpaths, the stroke cap controls the exposed endpoints. Path geometry
may draw outside the element's layout bounds; use a Frame or Mask when
that overflow should be clipped.
Properties
elements
Type: Property<Vec<PathElement>>
The path commands and control points, expressed in local coordinates.
stroke
The stroke applied along the path centerline.
fill
The fill applied to the interior of closed contours.
material
Light-reactive surface response.
smoothing
Type: Property<PathSmoothing>
Optional curve smoothing applied before rendering path geometry.
draw_start
Start position of the visible stroke range over the path's total length.
draw_end
End position of the visible stroke range over the path's total length.
Implementations
curve_to
pub fn curve_to(path: Vec<PathElement>, h_x: Size, h_y: Size, x: Size, y: Size) -> Vec<PathElement>
Appends a quadratic Bézier curve with one control point.
line_to
pub fn line_to(path: Vec<PathElement>, x: Size, y: Size) -> Vec<PathElement>
Appends a straight line segment to the provided point.
start
pub fn start(x: Size, y: Size) -> Vec<PathElement>
Starts a new path at the provided point.
PathClose
Path child component that closes the current contour.
PathCurve
Path child component that inserts a quadratic curve control point.
Properties
x
Control point x-coordinate.
y
Control point y-coordinate.
PathLine
Path child component that inserts a straight line segment.
PathPoint
Path child component that inserts a PathElement::Point.
Properties
x
Point x-coordinate.
y
Point y-coordinate.
drawing::rectangle
Structs
CornerRadii
Corner radii, ordered clockwise from top-left.
Pax templates canonically use a list of one to four values such as
corner_radius=[12, 8, 4]. Lists expand using the same clockwise arity rules
as CSS border-radius; a uniform radius may elide the brackets as
corner_radius=12.
The zero-based positional ("magic index") contract depends on list arity:
[all][top-left/bottom-right, top-right/bottom-left][top-left, top-right/bottom-left, bottom-right][top-left, top-right, bottom-right, bottom-left]
A contextual named object remains available as explicit longhand:
corner_radius={ top_left: 12 top_right: 8 bottom_right: 4 bottom_left: 2 }.
The fully type-qualified constructor also remains valid when explicit type
syntax is useful: corner_radius=CornerRadii { top_left: 12 top_right: 8 bottom_right: 4 bottom_left: 2 }.
Properties
top_left
Top-left corner radius.
top_right
Top-right corner radius.
bottom_right
Bottom-right corner radius.
bottom_left
Bottom-left corner radius.
Implementations
radii
pub fn radii(top_left: Numeric, top_right: Numeric, bottom_right: Numeric, bottom_left: Numeric) -> Self
Constructs a CornerRadii value from clockwise corner radii.
Rectangle
A 2D vector rectangle, which covers its bounding box with the specified fill and stroke.
Properties
stroke
Stroke drawn around the rectangle.
fill
Fill painted inside the rectangle.
material
Light-reactive surface response.
corner_radius
Type: Property<CornerRadii>
Per-corner radii.
forms
Submodules
- forms::button
- forms::checkbox
- forms::combo_box
- forms::dialogs
- forms::dropdown
- forms::photo_picker
- forms::radio_list
- forms::slider
- forms::tabs
- forms::textbox
- forms::toast
- forms::tooltip
forms::button
Structs
Button
A button control, delegating to a platform-specific native button.
Properties
label
Type: Property<String>
Text label displayed inside the button.
color
Button background color.
hover_color
Button background color while hovered, when supported.
corner_radius
Type: Property<f64>
Button corner radius, in pixels.
outline
Button outline stroke.
style
Text style applied to the label.
forms::checkbox
Structs
Checkbox
A checkbox control, delegating to a platform-specific native checkbox.
Properties
background
The background color when unchecked
background_checked
The background color when checked
outline
The outline stroke of the checkbox
corner_radius
Type: Property<f64>
The border radius of the checkbox
checked
Type: Property<bool>
Whether the checkbox is currently checked
forms::combo_box
Structs
ComboBox
A text-filtered list control for selecting one item, with optional "new item" behavior.
Properties
text
Type: Property<String>
Text currently shown in the input.
selected
Type: Property<Option<usize>>
Selected option index, or None when there is no valid selection.
options
Type: Property<Vec<String>>
Available option labels.
new_item
Behavior when the typed text does not match any option.
background
Textbox/list background color.
stroke
Textbox/list stroke.
style
Text style for the textbox and list items.
corner_radius
Type: Property<f64>
Textbox corner radius, in pixels.
Enums
NewItem
Behavior when typed combo-box text does not match an existing option.
Variants
Disallow
Show "No items found" and do not allow adding a new item.
AllowInvalid
Allows invalid text in the text box on commit, setting selected to None.
Text(String)
Shows custom text when there are no matches; clicking it triggers the @new_item event.
forms::dialogs
Structs
ConfirmationDialog
A confirmation dialog, a modal dialog that asks the user to confirm an action with "Yes" or "No" buttons.
Properties
text
Type: Property<String>
Prompt text shown in the dialog.
open
Type: Property<bool>
Whether the dialog is visible.
forms::dropdown
Structs
Dropdown
A dropdown list control, delegating to a platform-specific native dropdown implementation. Allows the selection of a single option from a list of options.
Properties
stroke
Outline stroke for the dropdown control.
options
Type: Property<Vec<String>>
List of selectable option labels.
selected_id
Type: Property<u32>
Index of the currently selected option.
style
Text style for option labels.
background
Dropdown background color.
corner_radius
Type: Property<f64>
Dropdown corner radius, in pixels.
forms::photo_picker
Structs
PhotoPicker
Opens the platform photo picker when its slotted content is activated.
PhotoPicker renders its children as the visible affordance and layers a
transparent native hit target over them. Increment trigger to request a
programmatic open; on the web, direct user activation of the picker element is
the most reliable way to satisfy browser file-picker requirements.
Properties
trigger
Type: Property<u64>
Incrementing request value. Returned as request_id in photo_picker_change.
source
Type: Property<PhotoPickerSource>
Requested platform source.
allow_multiple
Type: Property<bool>
Whether multiple images may be selected.
accept
Type: Property<String>
Accepted MIME/file filter. Web uses this as the input accept value.
include_bytes
Type: Property<bool>
Whether the chassis should copy bytes into the event when practical.
max_bytes_per_photo
Type: Property<u64>
Maximum copied/read bytes per selected photo. Larger photos report a size-limit status.
Enums
PhotoPickerSource
Source requested when opening a PhotoPicker.
Variants
Library
Let the user choose existing images from the platform photo/file picker.
Camera
Request camera capture where the current platform supports it.
forms::radio_list
Structs
RadioList
A radio list control, delegating to a platform-specific native control.
Properties
background
Radio button background color when unchecked.
background_checked
Radio button background color when checked.
outline
Radio button outline stroke.
options
Type: Property<Vec<String>>
List of selectable option labels.
selected_id
Type: Property<u32>
Index of the currently selected option.
style
Text style for option labels.
forms::slider
Structs
Slider
A slider control, delegating to a platform-specific native range input.
Properties
background
Track background color.
accent
Accent color for the active track/thumb, when supported.
corner_radius
Type: Property<f64>
Slider corner radius, in pixels.
value
Type: Property<f64>
Current slider value.
step
Type: Property<f64>
Step interval.
min
Type: Property<f64>
Minimum value.
max
Type: Property<f64>
Maximum value.
forms::tabs
Structs
Tabs
A component displaying a list of tabs, e.g. for tabbed navigation.
Properties
names
Type: Property<Vec<String>>
A list of string labels for the tabs
selected
Type: Property<usize>
The index of the currently selected tab
color
The background color of the tabs
forms::textbox
Structs
Textbox
A text input field, with support for styling and font specification. Will be composited as a platform-specific
native element, for example an <input> element in the browser or a UITextField on iOS.
Properties
text
Type: Property<String>
Current text value.
background
Textbox background color.
placeholder
Type: Property<String>
Placeholder text shown when empty, when supported by the chassis.
stroke
Border stroke.
corner_radius
Type: Property<f64>
Corner radius, in pixels.
style
Text style.
outline
Focus outline stroke.
focus_on_mount
Type: Property<bool>
Requests focus when the textbox mounts.
multiline
Type: Property<bool>
Renders the textbox as a multiline text area when supported by the chassis.
forms::toast
Structs
Toast
A user interface "toast", a notification banner that appears for a short time and then disappears again.
Properties
shown
Type: Property<bool>
Whether the toast is currently shown.
message
Type: Property<String>
The message to display in the toast.
height
The height of the toast.
y_pos
The y position of the toast.
forms::tooltip
Structs
Tooltip
A simple hover tooltip that renders slotted content plus a floating text tip.
Properties
tip
Type: Property<String>
Tooltip text.
autosize
Type: Property<bool>
Automatically sizes the trigger wrapper to its slotted content when possible.
autosize_x
Type: Property<Option<bool>>
Optional override for whether autosize manages the x axis.
autosize_y
Type: Property<Option<bool>>
Optional override for whether autosize manages the y axis.
layout
Submodules
layout::carousel
Structs
Carousel
A paged scrolling container with native scroll snapping and optional page dots.
Each slotted child becomes one page. The carousel lays pages out along axis,
sizes each page with page_size, and binds its scroll position through an
internal Scroller.
Properties
axis
Type: Property<CarouselAxis>
Axis along which pages are laid out and snapped.
page_size
Size of each page along the scroll axis (defaults to 100%).
show_dots
Type: Property<bool>
Whether to show page-position dots when there is more than one page.
scroll_pos_x
Type: Property<f64>
Horizontal scroll position, in pixels.
scroll_pos_y
Type: Property<f64>
Vertical scroll position, in pixels.
Enums
CarouselAxis
Direction for carousel paging and scroll snapping.
Variants
Horizontal
Pages flow left-to-right.
Vertical
Pages flow top-to-bottom.
layout::resizable
Structs
Resizable
Divides slotted content into resizable horizontal or vertical sections.
dividers contains positions along the main axis. With n dividers,
Resizable expects n + 1 slot children.
Properties
dividers
Divider positions along the main axis.
direction
Type: Property<ResizableDirection>
Whether sections are split horizontally or vertically.
Enums
ResizableDirection
Axis direction for a Resizable split.
Variants
Vertical
Split content into top-to-bottom sections.
Horizontal
Split content into left-to-right sections.
layout::stacker
Structs
ContainerReflowTransition
Reflow animation applied to surviving children when the stack layout changes.
Properties
kind
Type: Property<ContainerReflowTransitionKind>
Which reflow animation source to use.
frames
Type: Property<u64>
Duration in frames for Ease.
curve
Type: Property<ContainerReflowCurve>
Curve used for Ease.
name
Type: Property<String>
Reserved for a future named motion-curve lookup when kind is Named.
Stacker
Stacker lays out a series of nodes either
vertically or horizontally (i.e. a single row or column) with a specified gutter in between
each node. Stackers can be stacked inside of each other, horizontally
and vertically, along with percentage-based positioning and Transform2D.anchor to compose any rectilinear 2D layout.
Properties
direction
Type: Property<StackerDirection>
The direction the stacker should flow its cells
gutter
Spacing between cells
autosize
Type: Property<bool>
When true, the stacker uses content-child bounds to autosize its cells and, when possible, its own bounds as well.
Stacker interprets plain autosize=true as "autosize the extending
axis only" (y for vertical stacks, x for horizontal stacks). Use
autosize_x / autosize_y to override those per-axis defaults.
The underlying shrink-sizing pass only applies when the measured axis can be resolved without parent-size cycles, so percent-sized children continue to use the existing top-down layout behavior unless the corresponding stacker axis is already explicit.
autosize_x
Type: Property<Option<bool>>
Optional override for whether autosize manages the x axis.
autosize_y
Type: Property<Option<bool>>
Optional override for whether autosize manages the y axis.
sizes
Type: Property<Vec<Option<Size>>>
Size of each cell, by index. None-values (or array-index out-of-bounds values) will fall back to computed, equal-sizing
exit_mode
Type: Property<ContainerExitMode>
Whether exiting children stay in normal stack flow or hold their previous frame as ghosts.
reflow_transition
Type: Property<ContainerReflowTransition>
How surviving children should move when the stack's layout changes.
Defaults to Snap so Stackers remain a stable layout primitive unless
reflow motion is explicitly requested.
Enums
ContainerExitMode
Whether exiting children remain in normal layout flow or become ghosts.
Variants
Flow
Keep exiting children in the stack's in-flow layout until their out-transition finishes.
Ghost
Hold exiting children at their previous frame as overlays while the remaining children resolve layout without them.
ContainerReflowCurve
Easing curve used by ContainerReflowTransitionKind::Ease.
Variants
Linear
Hold
InQuad
OutQuad
InOutQuad
InBack
OutBack
InOutBack
ContainerReflowTransitionKind
Which reflow animation source to use when children move to new stack positions.
Variants
Snap
Snap immediately to the new layout.
Ease
Use a duration and easing curve.
Named
Reserved for a future named motion-curve lookup in the current component scope.
StackerDirection
Flow direction for a Stacker.
Variants
Vertical
Stack children top-to-bottom.
Horizontal
Stack children left-to-right.
layout::table
Structs
Cell
Selects one cell from a parent Table.
Properties
x
Type: Property<usize>
Zero-based column index.
y
Type: Property<usize>
Zero-based row index.
Col
Selects one column from a parent Table.
Properties
x
Type: Property<usize>
Zero-based column index.
Row
Selects one row from a parent Table.
Properties
y
Type: Property<usize>
Zero-based row index.
Span
Selects a rectangular region from a parent Table.
Properties
x
Type: Property<usize>
Zero-based column index.
y
Type: Property<usize>
Zero-based row index.
w
Type: Property<usize>
Number of columns to span.
h
Type: Property<usize>
Number of rows to span.
Table
A simple grid container for positioning children by rows and columns.
Table establishes row/column counts in local store. Child components such
as Row, Col, Cell, and Span read that context to size and position
their own slotted content.
Properties
rows
Type: Property<usize>
Number of table rows.
columns
Type: Property<usize>
Number of table columns.
media
Submodules
media::image
Structs
Image
A GPU/canvas-rendered image decoded by the active chassis.
Image draws into the node bounds and participates in the same canvas
rendering path as vectors. Use NativeImage when a platform-native image
element is preferable.
Properties
source
Type: Property<ImageSource>
Image source: empty, URL, or raw RGBA data.
fit
How the image should fit into this node's bounds.
Enums
ImageFit
Image fit/layout options.
Variants
Fill
Scale the image to fill its bounds, possibly clipping part of the image.
Fit
Scale the image to fit within its bounds without clipping, possibly leaving empty space.
Stretch
Stretch the image to exactly match the container.
ImageSource
Source data for an Image.
In Pax templates, a string in an ImageSource context is shorthand for
[ImageSource::Url]:
<Image source="assets/spaceship.png" />
<Image source={avatar_url} />
[ImageSource::Url] and [ImageSource::Data] remain available as explicit
constructor forms. Unlike the list shorthands used by compound Pax types,
the URL shorthand has no positional ("magic index") fields: the entire
string is the URL or chassis-relative asset path.
Variants
Empty
No image.
Url(String)
Image loaded from a URL/path understood by the chassis.
Data(usize, usize, Vec<u8>)
Raw RGBA image data: width, height, and bytes where len = width * height * 4.
media::native_image
Structs
NativeImage
A platform-native image. This will be managed by Pax's compositor as a platform-specific
host for an image, for example a DOM <img> element on the web, or a UIImageView on iOS.
NativeImage is an alternative to using pax_std::media::Image, with various trade-offs,
where the latter is managed as a GPU texture and drawn on a canvas surface.
Properties
url
Type: Property<String>
Image URL/path understood by the chassis.
fit
How the image should fit into this node's bounds.
media::youtube_video
Structs
YoutubeVideo
A web-based video player that can play YouTube videos given an embed URL.
Properties
url
Type: Property<String>
YouTube embed URL, such as https://www.youtube.com/embed/o8pkhbyb1Yo.
reference
Submodules
reference::example_host
Structs
ExampleHost
A display wrapper for documentation/demo examples.
ExampleHost renders every projected child with slot() and offers an
optional source drawer driven by an explicit sources manifest.
Properties
title
Type: Property<String>
Title shown in the source drawer.
sources
Type: Property<Vec<ExampleSource>>
Explicit source files related to the projected example subtree.
selected_source
Type: Property<usize>
Selected source index for the drawer tabs.
drawer_open
Type: Property<bool>
Whether the source drawer is visible.
Implementations
on_pre_render
pub fn on_pre_render(&mut self, ctx: &NodeContext)
Keeps the animated drawer progress synchronized with external state writes.
on_resize_mouse_down
pub fn on_resize_mouse_down(&mut self, ctx: &NodeContext, event: Event<MouseDown>)
Starts dragging the source divider when pressed near the handle.
on_resize_mouse_move
pub fn on_resize_mouse_move(&mut self, ctx: &NodeContext, event: Event<MouseMove>)
Updates the fixed-sum preview/source split while dragging.
on_resize_mouse_out
pub fn on_resize_mouse_out(&mut self, ctx: &NodeContext, _event: Event<MouseOut>)
Restores the cursor after leaving the source divider.
on_resize_mouse_over
pub fn on_resize_mouse_over(&mut self, ctx: &NodeContext, _event: Event<MouseOver>)
Shows the platform resize cursor over the source divider.
on_resize_mouse_up
pub fn on_resize_mouse_up(&mut self, ctx: &NodeContext, _event: Event<MouseUp>)
Ends source divider dragging.
on_split_mouse_down
pub fn on_split_mouse_down(&mut self, ctx: &NodeContext, _event: Event<MouseDown>)
Starts dragging from the explicit source divider hit target.
toggle_drawer
pub fn toggle_drawer(&mut self, _ctx: &NodeContext, _event: Event<Click>)
Toggles the source drawer.
ExampleSource
One source file shown by ExampleHost.
Properties
label
Type: String
File label shown in drawer tabs.
language
Type: String
Language label shown in the drawer.
code
Type: String
Source code contents.
Maintainer Reference
This reference is for work on Pax itself and for extensions that need its lower-level interfaces. It covers the language, compiler representation, runtime, platform messages, and renderer. Application authors can usually stay with the guide chapters and public API reference.
For a first look inside the engine, read How Pax Runs. If you are implementing an element through runtime hooks, begin with Primitives, then use the runtime reference and source to follow the relevant lifecycle and rendering contract.
Source contracts and design notes
The generated pages below describe Rust declarations and their documentation comments at this source revision. Internal interfaces evolve with the engine; inspect the implementation and tests for the Pax version you are extending. In particular, check debug and release paths when changing information that crosses the compiler/runtime boundary.
The Runtime and Cartridge Notes are a historical design appendix. They include proposed directions and older packaging models; their status is different from a tested current API. Internal design records elsewhere in the repository likewise need to be read in the context of their implementation status. For current build artifacts, use Targets, Build, and Deployment.
Crates
The crate names follow the engine boundary: pax-language handles Pax syntax
and expressions, pax-manifest holds program representations, pax-runtime
executes the scene, pax-message defines platform messages, and pax-gpu
implements the GPU renderer.
pax-language
Submodules
Structs
PaxParser
Pest parser generated from the Pax grammar.
Enums
Rule
Variants
EOI
End-of-input
WHITESPACE
comment
pax_component_definition
/// ////// ////// BEGIN TEMPLATE ///
root_tag_pair
any_tag_pair
open_tag
closing_tag
self_closing_tag
matched_tag
inner_nodes
identifier
pascal_identifier
event_id
attribute_key_value_pair
class_attribute
class_value
class_string_list
attribute_transition_binding
attribute_event_binding
transition_id
transition_binding_value
transition_inline_timeline_value
transition_inline_timeline_body
double_binding
any_template_value
node_inner_content
string
inner
char
settings_block_declaration
/// ////// ////// BEGIN SETTINGS ///
settings_block_element
settings_conditional
settings_if_branch
settings_else_if_branch
settings_else_branch
settings_conditional_body
selector_block
literal_object
selector
settings_key_value_pair
settings_event_binding
settings_key
settings_value
timeline_block_declaration
/// ////// ////// BEGIN TIMELINES ///
timeline_block_setting
timeline_block_setting_value
timeline_selector_block
timeline_selector_body
timeline_property_key_value_pair
timeline_inline_value
timeline_track
timeline_keyframe
timeline_keyframe_value
timeline_marker
timeline_percent
timeline_duration
timeline_duration_unit
timeline_easing_curve
timeline_target
timeline_local_target
timeline_symbol
gradient_inline_value
/// ////// ////// BEGIN GRADIENTS ///
gradient_body
gradient_shape_block
gradient_shape_key
gradient_shape_settings
gradient_shape_setting
gradient_shape_setting_value
gradient_stop
gradient_stop_marker
gradient_stop_value
literal_function
silent_comma
function_list
literal_value
literal_boolean
literal_some
literal_none
literal_option
literal_number_with_unit
literal_number
literal_number_integer
literal_number_float
literal_number_unit
literal_tuple
literal_tuple_access
literal_list
literal_list_access
literal_enum_value
literal_enum_args_list
literal_color
/// ////// ////// BEGIN COLORS ///
literal_color_space_func
literal_color_channel
xo_color_space_func
literal_color_const
expression_body
/// ////// ////// BEGIN EXPRESSIONS This sub-grammar describes PAXEL, the Pax Expression Language ///
expression_ternary
expression_coalesce
expression_binary
expression_wrapped
expression_grouped
expression_grouped_unit
xo_primary
xo_prefix
xo_neg
xo_bool_not
xo_infix
xo_add
xo_bool_and
xo_bool_or
xo_div
xo_exp
xo_mod
xo_mul
xo_rel_eq
xo_rel_gt
xo_rel_gte
xo_rel_lt
xo_rel_lte
xo_rel_neq
xo_sub
xo_null_coalesce
xo_tern_then
xo_tern_else
xo_range
xo_range_exclusive
xo_literal
xo_object
xo_object_settings_key_value_pair
xo_symbol
xo_tuple
xo_list
xo_enum_or_function_call
xo_enum_or_function_args_list
statement_control_flow
/// ////// ////// BEGIN CONTROL FLOW ///
statement_if
statement_if_branch
statement_else_if_branch
statement_else_branch
statement_for
statement_slot
statement_for_predicate_declaration
statement_for_source
statement_for_key
Functions
parse_pax_err
pub fn parse_pax_err(expected_rule: Rule, input: &str) -> Result<Pair<'_, Rule>, Error<Rule>>
Parse a string against a Pax grammar rule, preserving the structured pest error.
parse_pax_pairs
pub fn parse_pax_pairs(expected_rule: Rule, input: &str) -> Result<Pairs<'_, Rule>, Error<Rule>>
Parse a string into pest pairs for a Pax grammar rule.
parse_pax_str
pub fn parse_pax_str(expected_rule: Rule, input: &str) -> Result<Pair<'_, Rule>, String>
Parse a string against a single Pax grammar rule, returning a human-readable error string.
deserializer
Submodules
Structs
PaxDeserializer
Serde bridge from Pax literal grammar nodes into runtime values.
Properties
ast
Type: Pair<'de, Rule>
Implementations
from
pub fn from(ast: Pair<'de, Rule>) -> Self
Wrap a pest pair as a deserializer.
Functions
from_pax
pub fn from_pax(str: &str) -> Result<PaxValue>
Deserialize a Pax literal string into a runtime PaxValue.
from_pax_ast
pub fn from_pax_ast(ast: Pair<'_, Rule>) -> Result<PaxValue>
Deserialize a parsed literal AST node into a runtime PaxValue.
deserializer::error
Enums
Error
Error emitted while converting Pax literal syntax into runtime values.
Variants
Message(String)
UnsupportedType(String)
Type Aliases
Result
Result type used by the Pax literal deserializer.
formatting
Structs
FormatSummary
Result of formatting a file or directory tree.
Properties
files_checked
Type: usize
Pax-bearing source files inspected.
changed_files
Type: Vec<PathBuf>
Files whose canonical representation differs from disk.
Functions
format_file
pub fn format_file(file_path: &str) -> Result<(), Report>
Format either a .pax file or an inlined Pax template inside a Rust file.
format_path
pub fn format_path(path: &Path, check: bool) -> Result<FormatSummary, Report>
Format a .pax/.rs file or every Pax-bearing source file below a directory.
Directory traversal skips generated and dependency directories (.git, .pax,
target, and node_modules). When check is true, files are inspected but not
written, and changed paths are returned in [FormatSummary::changed_files].
format_pax_template
pub fn format_pax_template(code: String) -> Result<String, Report>
Format one Pax template string.
helpers
Structs
InlinedTemplate
Source span and contents for a Pax template embedded in an #[inlined(...)] attribute.
Properties
struct_name
Type: String
start
Type: (usize, usize)
end
Type: (usize, usize)
template
Type: String
InlinedTemplateFinder
AST visitor that extracts #[inlined(...)] templates from #[pax] structs.
Properties
file_contents
Type: String
templates
Type: Vec<InlinedTemplate>
Implementations
new
pub fn new(file_contents: String) -> Self
Prepare a finder for one Rust source file.
Functions
clear_inlined_template
pub fn clear_inlined_template(file_path: &str, pascal_identifier: &str)
Replace a matching #[inlined(...)] template with an empty template body.
get_substring_by_line_column
pub fn get_substring_by_line_column(input: &str, start: (usize, usize), end: (usize, usize)) -> Option<String>
Extract a source substring addressed by one-indexed line/column coordinates.
replace_by_line_column
pub fn replace_by_line_column(input: &str, start: (usize, usize), end: (usize, usize), replacement: String) -> Option<String>
Replace a source span addressed by one-indexed line/column coordinates.
interpreter
Submodules
Structs
PaxIdentifier
Symbol reference in a PAXEL expression.
Properties
name
Type: String
Implementations
new
pub fn new(name: &str) -> Self
Construct an identifier from its source spelling.
PaxInfix
Binary infix operation with left and right expression operands.
Implementations
lhs
pub fn lhs(&self) -> &PaxExpression
Left-hand expression.
operator_name
pub fn operator_name(&self) -> &str
Name of the infix operator.
rhs
pub fn rhs(&self) -> &PaxExpression
Right-hand expression.
PaxNullCoalesce
Short-circuiting fallback expression. Some(value) ?? fallback evaluates to
value, None ?? fallback evaluates the fallback, and non-option left
operands pass through unchanged.
Implementations
lhs
pub fn lhs(&self) -> &PaxExpression
Left-hand expression.
rhs
pub fn rhs(&self) -> &PaxExpression
Right-hand fallback expression.
PaxOperator
Parsed operator token, stored by display name.
Implementations
name
pub fn name(&self) -> &str
Source spelling for this operator.
new
pub fn new(name: impl Into<String>) -> Self
Construct an operator from its source spelling.
PaxPostfix
Postfix operation node.
Implementations
lhs
pub fn lhs(&self) -> &PaxExpression
Left-hand expression.
operator_name
pub fn operator_name(&self) -> &str
Name of the postfix operator.
PaxPrefix
Prefix operation such as numeric negation or boolean not.
Implementations
operator_name
pub fn operator_name(&self) -> &str
Name of the prefix operator.
rhs
pub fn rhs(&self) -> &PaxExpression
Right-hand expression.
PaxTernary
Conditional expression with a boolean condition and selected true/false branch.
Implementations
condition
pub fn condition(&self) -> &PaxExpression
Condition expression.
else_branch
pub fn else_branch(&self) -> &PaxExpression
Expression evaluated when the condition is false.
then_branch
pub fn then_branch(&self) -> &PaxExpression
Expression evaluated when the condition is true.
Enums
PaxAccessor
Access path applied after an identifier, such as .field, .0, or [index].
Variants
Tuple(usize)
List(PaxExpression)
Struct(String)
PaxExpression
PAXEL expression AST node.
Variants
Primary(Box<PaxPrimary>)
Prefix(Box<PaxPrefix>)
Infix(Box<PaxInfix>)
Postfix(Box<PaxPostfix>)
Ternary(Box<PaxTernary>)
NullCoalesce(Box<PaxNullCoalesce>)
Implementations
infix
pub fn infix(lhs: PaxExpression, operator: impl Into<String>, rhs: PaxExpression) -> Self
Construct an infix expression.
null_coalesce
pub fn null_coalesce(lhs: PaxExpression, rhs: PaxExpression) -> Self
Construct a null-coalescing expression.
postfix
pub fn postfix(lhs: PaxExpression, operator: impl Into<String>) -> Self
Construct a postfix expression.
prefix
pub fn prefix(operator: impl Into<String>, rhs: PaxExpression) -> Self
Construct a prefix expression.
ternary
pub fn ternary(condition: PaxExpression, then_branch: PaxExpression, else_branch: PaxExpression) -> Self
Construct a ternary expression.
PaxPrimary
Primary expression forms: literals, symbols, object/list/tuple literals, calls, and ranges.
Variants
Literal(PaxValue)
Grouped(Box<PaxExpression>, Option<PaxUnit>)
Identifier(PaxIdentifier, Vec<PaxAccessor>)
Object(Vec<(String, PaxExpression)>)
FunctionOrEnum(String, String, Vec<PaxExpression>)
Range(PaxExpression, PaxExpression)
Tuple(Vec<PaxExpression>)
List(Vec<PaxExpression>)
PaxUnit
Unit suffix attached to a grouped numeric expression.
Variants
Percent
Pixels
Radians
Degrees
Milliseconds
Seconds
Frames
Functions
compute_paxel
pub fn compute_paxel(expr: &str, idr: Rc<dyn IdentifierResolver>) -> Result<PaxValue, String>
Compute a pax expression to a PaxValue
parse_pax_expression
pub fn parse_pax_expression(expr: &str) -> Result<PaxExpression, String>
Parse a pax expression into a computable AST
parse_pax_expression_from_pair
pub fn parse_pax_expression_from_pair(expr: Pair<'_, Rule>) -> Result<PaxExpression, String>
Parse an already-produced pest pair into a PAXEL expression AST.
interpreter::property_resolution
Traits
DependencyCollector
Walk an expression tree and report the symbols that should dirty-watch it.
IdentifierResolver
Trait for resolving identifiers to values This is implemented by RuntimePropertyStackFrame
pax-runtime
Submodules
api
Structs
NodeContext
Runtime context passed into user component lifecycle methods and event handlers.
Child-related fields intentionally separate semantic payload from engine transport:
projected_childrenis the raw transport family used bySlotreceived_childrenis the normalized semantic payload that this node should treat as content from its callerretained_received_childrenare former received children kept alive only so@outtransitions can finish
A node's own private template or primitive-assembled structure is intentionally not surfaced here as a first-class "child family" for container consumers.
Properties
expanded_node
Type: Weak<ExpandedNode>
slot_index
Type: Property<Option<usize>>
slot index of this node in its container
local_stack_frame
Type: Rc<RuntimePropertiesStackFrame>
Stack frame of this component, used to look up stores
containing_component
Type: Weak<ExpandedNode>
Reference to the ExpandedNode of the component containing this node
elapsed_frames
Type: Property<u64>
The current global engine frame count.
elapsed_millis
Type: Property<u64>
The current global engine wall-clock time in milliseconds.
gyro
Current device orientation sensor reading.
accel
Current device accelerometer reading.
bounds_parent
Type: Property<(f64, f64)>
The bounds of this element's immediate container (parent) in px
bounds_self
Type: Property<(f64, f64)>
The bounds of this element in px
measured_size
Type: Property<Option<(f64, f64)>>
Measured bounds resolved by the chassis or container layout for this node.
subtree_layout_hull
Type: Property<LayoutHull>
Node-local subtree layout hull published by the engine for container measurement.
platform
Type: Platform
Current platform (Web/Native) this app is running on
os
Type: OS
Current os (Android/Windows/Mac/Linux) this app is running on
target
Type: Property<TargetInfo>
Derived target facts for platform/OS checks.
viewport
Derived viewport facts for size and orientation checks.
projected_children_count
Type: Property<usize>
The number of projected children available to this node.
This is the raw transport count used by slot-driven implementations.
Container-style consumers usually want received_children_count
instead.
node_transform_and_bounds
Type: TransformAndBounds<NodeLocal, Window>
The transform of this node in the global coordinate space
projected_children
Type: Property<Vec<Rc<ExpandedNode>>>
Children projected into this node from the containing component.
Projection is an engine transport mechanism. Consumers that want the
semantic payload owned by this node should prefer received_children.
projected_children_changed
Type: Property<()>
A structural invalidation signal for projected children.
received_children
Type: Property<Vec<Rc<ExpandedNode>>>
Semantic payload children received by this node from its caller.
This is the canonical "content" view for container-style logic. It
excludes private encapsulated implementation children and also excludes
exit-retained payload nodes, which instead appear in
retained_received_children.
received_children_count
Type: Property<usize>
Convenience count derived from received_children.
received_children_changed
Type: Property<()>
A structural invalidation signal for received_children.
Prefer this or received_children itself for structural subscriptions
that must react to reorders as well as insertions and removals.
retained_received_children
Type: Property<Vec<Rc<ExpandedNode>>>
Received children retained only so exit transitions can finish.
These are no longer part of the active semantic payload, but some
containers still need to place them as ghosts or overlays while their
@out transitions run.
retained_received_children_changed
Type: Property<()>
A structural invalidation signal for retained_received_children.
Implementations
clear_subscriptions
pub fn clear_subscriptions(&self)
Remove all subscriptions registered on this node.
dispatch_event
pub fn dispatch_event(&self, identifier: &'static str) -> Result<(), String>
Queue a named custom event from this component for dispatch at the end of the tick.
elapsed_time_millis
pub fn elapsed_time_millis(&self) -> u128
Milliseconds elapsed according to the chassis-provided clock.
get_node_interface
pub fn get_node_interface(&self) -> Option<NodeInterface>
Return the interface for this node's containing component, when present.
get_screenshot_map
pub fn get_screenshot_map(&self) -> Rc<RefCell<HashMap<u32, ScreenshotData>>>
Shared map where completed screenshot captures are published by id.
local_point
pub fn local_point(&self, p: Point2<Window>) -> Point2<NodeLocal>
Convert a window-space point into this node's local coordinate space, including any presentation offsets inherited from ancestor scrollers.
navigate_to
pub fn navigate_to(&self, url: &str, target: NavigationTarget)
Ask the chassis to navigate to a URL.
On web targets, same-origin navigation in the current tab can be handled through the browser History API and routed back into Pax without a full page reload. Other targets use the active chassis navigation behavior.
peek_local_store
pub fn peek_local_store<T: Store, V>(&self, f: impl FnOnce(&mut T) -> V) -> Result<V, String>
Borrow the nearest stack-local store of type T.
push_local_store
pub fn push_local_store<T: Store>(&self, store: T)
Push component-local state onto the runtime stack for descendants to find.
screenshot
pub fn screenshot(&self, id: u32)
Request a screenshot capture from the chassis, keyed by caller-provided id.
set_cursor
pub fn set_cursor(&self, cursor: CursorStyle)
Ask the chassis to display the requested cursor over the app surface.
subscribe
pub fn subscribe(&self, dependencies: &[UntypedProperty], f: impl Fn() + 'static)
Attach a dependency subscription whose callback runs when any dependency dirties.
cartridge
Structs
ComponentPropertyDescriptor
Runtime descriptor for applying all resolved layers of a generated component property.
Properties
name
Type: &'static str
Property name as it appears in Pax templates and settings.
apply_entries
Type: fn()
Generated applicator for all resolved layers of this property.
Implementations
new
pub const fn new(name: &'static str, apply_entries: fn()) -> Self
Creates a descriptor for a generated component property.
TemplatePropertyPlan
Immutable template inputs shared by common and component property binding. Resolved columns and reactive properties are always node-local; this plan never caches evaluated values or selector matches across nodes or rebinds.
Functions
apply_component_property
pub fn apply_component_property<T>(property: &mut Property<T>, name: &str, value_definition: &ValueDefinition, stack: &Rc<RuntimePropertiesStackFrame>, timeline_stack: Rc<RuntimePropertiesStackFrame>, build_block: fn()) where T: CoercionRules + PropertyValue + ToPaxValue
Replaces a typed component property with the value produced from a single value definition.
build_component_property
pub fn build_component_property<T>(name: &str, value_definition: &ValueDefinition, stack: &Rc<RuntimePropertiesStackFrame>, timeline_stack: Rc<RuntimePropertiesStackFrame>, build_block: fn()) -> Property<T> where T: CoercionRules + PropertyValue + ToPaxValue
Builds a typed component property from a single value definition. Callers
that apply layered settings should bind $base before calling this helper.
create_new_common_properties
pub fn create_new_common_properties(defined_properties: &BTreeMap<String, ValueDefinition>, stack_frame: &Rc<RuntimePropertiesStackFrame>) -> Rc<RefCell<CommonProperties>>
Creates common properties from a flattened property map.
create_new_common_properties_from_columns
pub fn create_new_common_properties_from_columns(property_columns: &RuntimeResolvedPropertyColumns, stack_frame: &Rc<RuntimePropertiesStackFrame>) -> Rc<RefCell<CommonProperties>>
Creates common properties from resolved columns, preserving layer order so
$base can reference each prior layer.
property_columns_from_defined_properties
pub fn property_columns_from_defined_properties(defined_properties: &BTreeMap<String, ValueDefinition>) -> RuntimeResolvedPropertyColumns
Converts a flattened property map into one-entry columns for legacy callers that do not participate in selector/import precedence layering.
stack_with_base
pub fn stack_with_base<T>(stack: &Rc<RuntimePropertiesStackFrame>, base_property: Property<T>) -> Rc<RuntimePropertiesStackFrame> where T: PropertyValue + ToPaxValue
Returns a stack frame with $base bound to the supplied previous-layer
property value.
stack_with_optional_base
pub fn stack_with_optional_base<T>(stack: &Rc<RuntimePropertiesStackFrame>, base_property: Property<Option<T>>) -> Rc<RuntimePropertiesStackFrame> where T: PropertyValue + ToPaxValue
Returns a stack frame with $base bound to an optional previous common
property value, exposing T::default() when the previous layer is None.
update_existing_common_properties
pub fn update_existing_common_properties(expanded_node: &Rc<ExpandedNode>, defined_properties: &BTreeMap<String, ValueDefinition>, stack_frame: &Rc<RuntimePropertiesStackFrame>)
Applies a flattened common-property map to an existing expanded node.
update_existing_common_properties_from_columns
pub fn update_existing_common_properties_from_columns(expanded_node: &Rc<ExpandedNode>, property_columns: &RuntimeResolvedPropertyColumns, stack_frame: &Rc<RuntimePropertiesStackFrame>)
Applies resolved common-property columns to an existing expanded node,
preserving layer order so $base can reference each prior layer.
Constants
BASE_SYMBOL
PAXEL symbol bound while resolving a property layer to the value from the preceding layer in that same property's precedence stack.
component
Structs
ComponentInstance
A render node with its own runtime context. Will push a frame
to the runtime stack including the specified projected_children and
a PaxType properties object. Component is used at the root of
applications, at the root of reusable components like Stacker, and
in special applications like Repeat where it houses the RepeatItem
properties attached to each of Repeat's virtual nodes.
Properties
template
Type: InstanceNodePtrList
timelines
Type: Vec<Rc<RefCell<Timeline>>>
ScrollPosition
Built-in $scroll_position value synthesized for components inside scrollers.
Properties
x
Type: f64
y
Type: f64
Implementations
create_builtin_if_exists
pub fn create_builtin_if_exists(property_scope: Ref<'_, HashMap<String, Variable>>) -> Option<HashMap<String, Variable>>
Create a stack frame containing $scroll_position when scroll properties are present.
conditional
Structs
ConditionalInstance
A special "control-flow" primitive, Conditional (if) allows for a
subtree of a component template to be rendered conditionally,
based on the value of the property boolean_expression.
The Pax compiler handles ConditionalInstance specially
with the if syntax in templates.
ConditionalProperties
Contains the expression of a conditional, evaluated as an expression.
Properties
boolean_expression
Type: Property<bool>
conditional_branches
Type: Vec<Property<bool>>
container
Container-facing child ontology and geometry seams. Container-facing child ontology and geometry seams.
The runtime carries several child concepts, but container consumers should reason about them on two axes:
- Semantic role
received_children: the payload a node received from its caller- encapsulated implementation children: the node's own private template or primitive-assembled structure
- Lifecycle slice of the received payload
- active received children: present in
NodeContext::received_children - retained exiting received children: present in
NodeContext::retained_received_children
- active received children: present in
Engine details such as projection still exist, but they are transport mechanisms rather than the primary semantic abstraction.
Structs
ContainerFrame
Parent-local frame assigned by a container to one of its received children.
This behaves like a virtual wrapper node inside the parent: the frame's transform is composed onto the parent transform and its bounds become the child container bounds.
Properties
transform
Type: Transform2<NodeLocal, NodeLocal>
bounds
Type: (f64, f64)
Enums
ReceivedChildrenSource
Engine-internal selector for which child family should be normalized into
NodeContext::received_children.
This is a provenance selector, not the semantic API surface.
Owned means the node's received payload already lives in its active child
tree.
Projected means the node receives payload from its caller and the runtime
threads that payload through projection so it can be consumed by slot(...)
within the node's encapsulated implementation.
Variants
Owned
Projected
Traits
Container
Trait for nodes that semantically interpret child content.
Containers should treat NodeContext::received_children as their canonical
payload set and NodeContext::retained_received_children as the set of
exit-retained payload nodes that may still need placement or transition
handling.
Encapsulated implementation children remain a private detail of the node's own template or primitive assembly and should generally not drive container layout logic.
Containers can call this from their existing mount logic to install reactive behavior on top of those normalized views.
Functions
bind_content_measurement_effect
pub fn bind_content_measurement_effect<F>(expanded_node: &Rc<ExpandedNode>, ctx: &NodeContext, listener_name: &'static str, geometry: ContentMeasurementGeometry, extra_deps: &[UntypedProperty], effect: F) where F: Fn(&Rc<ExpandedNode>, &NodeContext) + Clone + 'static
Bind a reactive content-measurement effect to this node.
The effect is re-evaluated after the tree update pass, before occlusion and layer-plan generation, and its dependency list is rebound whenever the normalized received-child list changes.
measure_content_children_forward_extents
pub fn measure_content_children_forward_extents(ctx: &NodeContext) -> (Option<f64>, Option<f64>)
Measure forward autosize extents from received content.
measure_content_children_layout_hull
pub fn measure_content_children_layout_hull(ctx: &NodeContext) -> LayoutHull
Measure the aggregate layout hull contributed by received content in the container's local coordinate space.
Empty content is treated as a zero-sized valid hull so autosized containers
can collapse to 0x0 when they have no children.
resolve_axis_autosize
pub fn resolve_axis_autosize(autosize: bool, axis_override: Option<bool>, default_when_enabled: bool) -> bool
Resolve one axis of autosize given the public autosize toggle plus an optional override.
resolve_content_autosize_measurement
pub fn resolve_content_autosize_measurement(ctx: &NodeContext, width_explicit: bool, height_explicit: bool) -> Option<(f64, f64)>
Resolve a node's measured size from its received content.
Explicit axes keep their current container bounds; implicit axes use the
measured forward extents when they are valid. If an implicit axis cannot be
measured safely, this returns None so the caller can fall back.
resolve_content_autosize_measurement_with_axes
pub fn resolve_content_autosize_measurement_with_axes(ctx: &NodeContext, width_explicit: bool, height_explicit: bool, autosize_width: bool, autosize_height: bool) -> Option<(f64, f64)>
Resolve a node's measured size from received content with explicit per-axis autosize control.
sync_content_autosize
pub fn sync_content_autosize(expanded_node: &Rc<ExpandedNode>, ctx: &NodeContext, enabled: bool)
Update measured_size from received content when autosize is enabled.
sync_content_autosize_with_axes
pub fn sync_content_autosize_with_axes(expanded_node: &Rc<ExpandedNode>, ctx: &NodeContext, autosize_width: bool, autosize_height: bool)
Update measured_size from received content with explicit per-axis autosize control.
engine
Submodules
- engine::layer_surface
- engine::layer_tiling
- engine::node_interface
- engine::occlusion
- engine::pax_gpu_render_context
- engine::piet_render_context
Structs
Globals
Engine-wide reactive globals exposed to every component frame.
Properties
elapsed_frames
Type: Property<u64>
elapsed_millis
Type: Property<u64>
viewport
Type: Property<TransformAndBounds<NodeLocal, Window>>
gyro
accel
route_location
Type: Property<RouteLocation>
browser_allows_scroller_vector_layers
Type: Property<bool>
browser_allows_nested_scroller_vector_layers
Type: Property<bool>
platform
Type: Platform
os
Type: OS
target
Type: TargetInfo
get_elapsed_millis
Type: Rc<dyn Fn() -> u128>
Implementations
stack_frame
pub fn stack_frame(&self) -> Rc<RuntimePropertiesStackFrame>
Build the root stack frame containing built-in globals plus internal engine state.
Handler
Runtime event handler thunk generated from template bindings.
Properties
function
Type: fn()
location
Type: HandlerLocation
Implementations
new_component_handler
pub fn new_component_handler(function: fn()) -> Self
Build a handler whose self argument is the containing component.
new_inline_handler
pub fn new_inline_handler(function: fn()) -> Self
Build a handler whose self argument is the inline primitive/component.
HandlerRegistry
Map from event key to one or more handlers registered on an instance node.
Properties
handlers
Type: HashMap<String, Vec<Handler>>
PaxEngine
Singleton struct storing everything related to properties computation & rendering
Properties
runtime_context
Type: Rc<RuntimeContext>
root_expanded_node
Type: Option<Rc<ExpandedNode>>
scroller_tiling_policy
Type: ScrollerTilingPolicy
Implementations
Central instance of the PaxEngine and runtime, intended to be created by a particular chassis. Contains all rendering and runtime logic.
mount_root_component
pub fn mount_root_component(&mut self, main_component_instance: Rc<ComponentInstance>) -> Rc<ExpandedNode>
Mount a root component tree into an existing runtime kernel.
set_viewport_size
pub fn set_viewport_size(&mut self, new_viewport_size: (f64, f64))
Called by chassis when viewport size changes, e.g. with native window resizes
tick
pub fn tick(&mut self) -> Vec<NativeMessage>
Workhorse methods of every tick. Will be executed up to 240 Hz. Three phases:
- Expand nodes & compute properties; recurse entire instance tree and evaluate ExpandedNodes, stitching together parent/child relationships between ExpandedNodes along the way.
- Compute layout (z-index & TransformAndBounds) by visiting ExpandedNode tree in rendering order, writing computed rendering-specific values to ExpandedNodes
- Render: a. find lowest node (last child of last node) b. start rendering, from lowest node on-up, throughout tree
unmount
pub fn unmount(&mut self)
Detach the mounted root component tree, leaving the runtime kernel empty.
Enums
HandlerLocation
Indicates whether a handler should receive inline-node or containing-component properties.
Variants
Inline
Component
engine::layer_surface
Structs
LayerSurfaceEntry
Desired surface geometry for one tile in a layer layout.
Properties
key
Type: String
host_signature
Type: String
origin_x
Type: f32
origin_y
Type: f32
replay_priority
Type: i32
surface
Type: LayerSurfaceSize
LayerSurfaceLayout
Desired set of physical surfaces for a logical layer.
Properties
surfaces
Type: Vec<LayerSurfaceEntry>
active
Type: bool
LayerSurfaceSize
Logical and backing-pixel dimensions for one physical surface.
Properties
logical_width
Type: f32
logical_height
Type: f32
surface_width
Type: u32
surface_height
Type: u32
dpr
Type: [f32; 2]
ReplayPriorityEntry
Retargeted surface metadata used to choose replay order without involving backend resources.
Properties
index
Type: usize
priority
Type: i32
SurfaceReplayCoordinator
Shared coordinator for physical-surface replay after a layer layout retarget.
This intentionally tracks renderer-agnostic surface indices and coverage bounds only. Backends remain responsible for applying the selected indices to their own renderer objects.
Functions
replay_batches_by_directional_priority
pub fn replay_batches_by_directional_priority(entries: &[ReplayPriorityEntry]) -> Vec<Vec<usize>>
Batch retargeted surfaces by planner priority, then by the leading row/column of travel.
engine::layer_tiling
Structs
LayerCanvasPlan
Canvas tiling plan for one logical render layer.
Properties
layer_id
Type: usize
active
Type: bool
surfaces
Type: Vec<SurfaceCanvasDescriptor>
SurfaceCanvasDescriptor
One physical canvas surface used to render a logical Pax layer tile.
Properties
id
Type: String
key
Type: String
left
Type: f64
top
Type: f64
width
Type: f64
height
Type: f64
replay_priority
Type: i32
surface_signature
Type: String
transform_signature
Type: String
host_signature
Type: String
Functions
scroller_canvas_plan
pub fn scroller_canvas_plan(layer_id: usize, host_signature: String, content_width: f64, content_height: f64, viewport_width: f64, viewport_height: f64, scroll_x: f64, scroll_y: f64, device_pixel_ratio: f64) -> LayerCanvasPlan
Build a tile window for a scrollable vector layer.
single_surface_plan
pub fn single_surface_plan(layer_id: usize, host_signature: String, width: f64, height: f64) -> LayerCanvasPlan
Build a one-surface plan for layers that do not need tiling.
engine::node_interface
Structs
NodeInterface
Runtime inspection handle for an expanded node.
Implementations
children
pub fn children(&self) -> Vec<NodeInterface>
Mounted child nodes.
containing_component
pub fn containing_component(&self) -> Option<NodeInterface>
Containing component for template scoping and slot ownership.
engine_id
pub fn engine_id(&self) -> ExpandedNodeIdentifier
Runtime-expanded id for this concrete node.
flattened_projected_children_count
pub fn flattened_projected_children_count(&self) -> Property<usize>
Reactive count of slot children after repeat/conditional flattening.
global_id
pub fn global_id(&self) -> Option<UniqueTemplateNodeIdentifier>
Compiler-global template id for this node, if it originated from a template node.
has_id
pub fn has_id(&self, id: &str) -> bool
Test the template id common property.
instance_flags
pub fn instance_flags(&self) -> InstanceFlags
Static flags from the node's instance.
is_descendant_of
pub fn is_descendant_of(&self, node: &NodeInterface) -> bool
True if this node is below node in the template-parent chain.
is_of_type
pub fn is_of_type<T: ToFromPaxAny>(&self) -> bool
Check whether the node's property object is of type T.
layout_properties
pub fn layout_properties(&self) -> LayoutProperties
Current layout properties after common-property collection.
measured_size
pub fn measured_size(&self) -> Option<(f64, f64)>
Measured bounds reported by a native or text-backed node.
render_parent
pub fn render_parent(&self) -> Option<NodeInterface>
Parent in render traversal order.
subtree_layout_hull
pub fn subtree_layout_hull(&self) -> Property<LayoutHull>
Node-local subtree hull published by the engine for container measurement.
template_parent
pub fn template_parent(&self) -> Option<NodeInterface>
Parent in template ownership order.
transform_and_bounds
pub fn transform_and_bounds(&self) -> Property<TransformAndBounds<NodeLocal, Window>>
Reactive transform-and-bounds property for this node.
with_properties
pub fn with_properties<V, T: ToFromPaxAny>(&self, f: impl FnOnce(&mut T) -> V) -> Option<V>
Borrow the node's typed property object if it has the requested type.
NodeLocal
Marker coordinate space for a node's local layout space.
engine::occlusion
Structs
OcclusionBox
Axis-aligned bounds used by the occlusion and native-mask pass.
Functions
update_node_occlusion
pub fn update_node_occlusion(root_node: &Rc<ExpandedNode>, ctx: &RuntimeContext)
Recompute z-order, native masks, and logical render-layer assignments for the tree.
engine::pax_gpu_render_context
Structs
LayerRenderer
Retained renderer bound to one physical surface tile for a logical layer.
Implementations
new
pub fn new(key: String, host_signature: String, renderer: WgpuRenderer<'static>, origin_x: f32, origin_y: f32, logical_width: f32, logical_height: f32, surface_width: u32, surface_height: u32, dpr: [f32; 2]) -> Self
Create a renderer wrapper with its current tile geometry.
renderer_mut
pub fn renderer_mut(&mut self) -> &mut WgpuRenderer<'static>
Access the underlying retained pax-gpu renderer.
sync_layout_metadata
pub fn sync_layout_metadata(&mut self, surface: &LayerSurfaceEntry)
Update the retained layout metadata after the owner has already applied matching backend surface/view transforms.
LayerTarget
Current renderer set for one logical layer.
Implementations
new
pub fn new(renderers: Vec<LayerRenderer>, active: bool) -> Self
Create a layer target from physical surface renderers.
renderers_mut
pub fn renderers_mut(&mut self) -> &mut [LayerRenderer]
Mutable access to each physical renderer backing this logical layer.
PaxGpuRenderer
Runtime RenderContext implementation backed by pax-gpu/wgpu.
Implementations
new
pub fn new(layer_factory: impl Fn(usize) -> Pin<Box<dyn Future>> + 'static) -> Self
Create a renderer that lazily asks the chassis for layer backends.
Enums
RenderLayerState
Lifecycle state for a lazily-created render layer.
Variants
Pending
Failed
Ready((LayerTarget, Pin<Box<dyn Fn() -> LayerSurfaceLayout>>))
Functions
convert_kurbo_to_lyon_path
pub fn convert_kurbo_to_lyon_path(kurbo_path: &BezPath) -> Path
Convert a kurbo path emitted by primitives into a lyon path consumed by pax-gpu.
to_pax_gpu_color
pub fn to_pax_gpu_color(color: &Color) -> Color
Convert a runtime API color into the pax-gpu render-context color.
engine::piet_render_context
Structs
PietLayerRenderer
Retained metadata for one piet-backed browser canvas surface.
PietLayerTarget
Current piet surface set for one logical layer.
PietRenderer
RenderContext implementation backed by piet.
Implementations
new
pub fn new(layer_factory: impl Fn(usize) -> (PietLayerTarget<R>, Box<dyn Fn() -> LayerSurfaceLayout>) + 'static) -> Self
Create a piet renderer with a chassis-provided logical layer factory.
layout
Structs
LayoutHull
Per-axis local extents contributed by a node subtree for container measurement.
Coordinates are expressed in the node's local layout space. Validity is tracked per axis so parent-dependent axes can be ignored without discarding the entire subtree.
Properties
min_x
Type: f64
max_x
Type: f64
min_y
Type: f64
max_y
Type: f64
valid_x
Type: bool
valid_y
Type: bool
LayoutProperties
Unresolved layout inputs copied out of common properties before geometry calculation.
Properties
x
Type: Option<Size>
y
Type: Option<Size>
width
Type: Option<Size>
height
Type: Option<Size>
rotate
Type: Option<Rotation>
scale_x
Type: Option<Percent>
scale_y
Type: Option<Percent>
anchor_x
Type: Option<Size>
anchor_y
Type: Option<Size>
skew_x
Type: Option<Rotation>
skew_y
Type: Option<Rotation>
Implementations
fill
pub fn fill() -> Self
Full-size defaults for nodes that should fill their containing bounds.
TransformAndBounds
Pax's canonical representation of position, size, and transform, encoded as a transform (translation, rotation, scale, skew) and a separate width/height (bounds) value. Bounds are expressed as the (x1, y1) values of the axis-aligned pre-transform bounding box, where (x0, y0) are the origin.
In this model, position is a derived property, calculated by applying the transform to the bounding box.
Properties
transform
Type: Transform2<F, T>
bounds
Type: (f64, f64)
Implementations
as_pure_scale
pub fn as_pure_scale(self) -> Self
Move bounds into the transform as scale, leaving unit bounds.
as_pure_size
pub fn as_pure_size(self) -> Self
Move scale from the transform into the bounds field.
as_transform
pub fn as_transform(&self) -> Transform2<F, T>
Convert this split representation into a single affine transform.
cast_spaces
pub fn cast_spaces<A: Space, B: Space>(self) -> TransformAndBounds<A, B>
Retype coordinate-space markers without changing numeric values.
center
pub fn center(&self) -> Point2<T>
Center point of this transformed box.
contains_point
pub fn contains_point(&self, point: Point2<T>) -> bool
Test whether a point falls inside this transformed box.
corners
pub fn corners(&self) -> [Point2<T>; 4]
Corners of this transformed box, starting at origin and proceeding around the rectangle.
intersects
pub fn intersects(&self, other: &Self) -> bool
Test transformed-box intersection using the separating axis theorem.
inverse
pub fn inverse(&self) -> TransformAndBounds<T, F>
Invert the transform-and-bounds mapping.
Functions
add_symmetric_padding_to_content_layout_hull
pub fn add_symmetric_padding_to_content_layout_hull(hull: LayoutHull, padding_x: Option<Size>, padding_y: Option<Size>) -> LayoutHull
Expand a content-space hull into the node's padded outer layout space.
Children are laid out inside the leading padding offset, but autosized bounds must be solved from the content hull itself. Solving the outer size first keeps percentage padding from feeding back through the node's current measured bounds.
apply_container_frame
pub fn apply_container_frame(container_transform_and_bounds: TransformAndBounds<NodeLocal, Window>, container_frame: Option<ContainerFrame>) -> TransformAndBounds<NodeLocal, Window>
Apply a container-assigned child frame on top of the parent geometry.
This is the geometry seam where container-owned placement can cooperate with descendant-authored layout and future bottom-up measurement.
apply_padding_frame
pub fn apply_padding_frame(container_transform_and_bounds: TransformAndBounds<NodeLocal, Window>, padding_x: Option<Size>, padding_y: Option<Size>) -> TransformAndBounds<NodeLocal, Window>
Apply a node's padding to the container geometry seen by its children.
calculate_transform_and_bounds
pub fn calculate_transform_and_bounds(_: &LayoutProperties, _: TransformAndBounds<NodeLocal, Window>) -> TransformAndBounds<NodeLocal, Window>
Resolve one set of layout properties into concrete bounds and a window-space transform.
compute_tab
pub fn compute_tab(layout_properties: Property<LayoutProperties>, extra_transform: Property<Option<Transform2D>>, container_transform_and_bounds: Property<TransformAndBounds<NodeLocal, Window>>) -> Property<TransformAndBounds<NodeLocal, Window>>
Compute a reactive TransformAndBounds property from layout properties plus parent geometry.
project_child_layout_hull_to_parent_space
pub fn project_child_layout_hull_to_parent_space(parent: TransformAndBounds<NodeLocal, Window>, child: TransformAndBounds<NodeLocal, Window>, child_hull: LayoutHull) -> LayoutHull
Project a child's local hull into its parent's local layout space.
project_layout_hull
pub fn project_layout_hull<F: Space, T: Space>(transform: Transform2<F, T>, hull: LayoutHull) -> LayoutHull
Project a local layout hull through the provided transform and return the axis-aligned hull in the destination coordinate space.
resolve_padded_autosize_axis
pub fn resolve_padded_autosize_axis(content_extent: f64, padding: Option<Size>) -> Option<f64>
Solve an autosized outer axis from measured content and symmetric padding.
properties
Structs
ExpandedNodeIdentifier
Stable runtime identifier assigned to an expanded node.
Properties
0
Type: u32
Implementations
to_u32
pub fn to_u32(&self) -> u32
Convert to the integer id passed across chassis message boundaries.
ExpressionContext
Data structure used for dynamic injection of values into Expressions, maintaining a pointer e.g. to the current stack frame to enable evaluation of properties & dependencies
Properties
stack_frame
Type: Rc<RuntimePropertiesStackFrame>
RuntimeContext
Shared context for properties pass recursion
Properties
layer_count
Type: Cell<usize>
dirty_canvases
Type: Rc<RefCell<Vec<bool>>>
Implementations
add_to_cache
pub fn add_to_cache(&self, node: &Rc<ExpandedNode>)
Add a node to runtime lookup caches.
canvas_node_light_mask
pub fn canvas_node_light_mask(&self, id: ExpandedNodeIdentifier) -> u32
Return the direct-light membership mask resolved for a retained canvas node.
capture_touch_target
pub fn capture_touch_target(&self, identifier: i64, target: ExpandedNodeIdentifier)
Route a touch sequence to the node hit at touch-down, even after the finger moves away.
captured_touch_target
pub fn captured_touch_target(&self, identifier: i64) -> Option<Rc<ExpandedNode>>
Resolve the node captured for an active touch sequence.
clear_all_dirty_canvases
pub fn clear_all_dirty_canvases(&self)
Mark every canvas layer clean.
clear_layer_scroller_owners
pub fn clear_layer_scroller_owners(&self)
Clear render-layer-to-scroller ownership before recomputing occlusion.
clear_root_expanded_node
pub fn clear_root_expanded_node(&self)
Clear the registered root expanded node.
clear_visual_viewport_state
pub fn clear_visual_viewport_state(&self)
Clear cached visual viewport state.
get_elements_beneath_ray
pub fn get_elements_beneath_ray(&self, root: Option<Rc<ExpandedNode>>, ray: Point2<Window>, limit_one: bool, accum: Vec<Rc<ExpandedNode>>, hit_invisible: bool) -> Vec<Rc<ExpandedNode>>
Simple 2D raycasting: the coordinates of the ray represent a
ray running orthogonally to the view plane, intersecting at
the specified point ray. Areas outside of clipping bounds will
not register a hit, nor will elements that suppress input events.
get_expanded_node_by_eid
pub fn get_expanded_node_by_eid(&self, id: ExpandedNodeIdentifier) -> Option<Rc<ExpandedNode>>
Look up an expanded node by runtime id.
get_expanded_nodes_by_global_ids
pub fn get_expanded_nodes_by_global_ids(&self, uni: &UniqueTemplateNodeIdentifier) -> Vec<Rc<ExpandedNode>>
Finds all ExpandedNodes with corresponding UniqueTemplateNodeIdentifier
get_expanded_nodes_by_id
pub fn get_expanded_nodes_by_id(&self, id: &str) -> Vec<Rc<ExpandedNode>>
Finds all ExpandedNodes with the CommonProperty#id matching the provided string
get_layer_scroller_owner
pub fn get_layer_scroller_owner(&self, layer_id: usize) -> Option<ExpandedNodeIdentifier>
Find the scroller that owns a render layer, when one exists.
get_root_scroller_id
pub fn get_root_scroller_id(&self) -> Option<u32>
Current page-scroll-backed root scroller id.
get_screenshot_map
pub fn get_screenshot_map(&self) -> Rc<RefCell<HashMap<u32, ScreenshotData>>>
Shared screenshot capture map keyed by request id.
get_scroller_surface_scroll
pub fn get_scroller_surface_scroll(&self, id: u32) -> Option<(f64, f64)>
Fetch the presentation scroll offset for a native scroller surface, falling back to the authoritative scroll position when presentation scroll is unavailable.
get_scroller_surface_state
pub fn get_scroller_surface_state(&self, id: u32) -> Option<ScrollerSurfaceState>
Fetch cached scroller surface state by node id.
get_topmost_element_beneath_ray
pub fn get_topmost_element_beneath_ray(self: &Rc<Self>, ray: Point2<Window>) -> Option<Rc<ExpandedNode>>
Alias for get_elements_beneath_ray with limit_one = true
get_visual_viewport_state
pub fn get_visual_viewport_state(&self) -> Option<VisualViewportState>
Return cached browser visual viewport state, if available.
is_canvas_dirty
pub fn is_canvas_dirty(&self, id: &usize) -> bool
Check whether a canvas layer needs redraw.
layer_has_canvas_drawables
pub fn layer_has_canvas_drawables(&self, layer: usize) -> bool
Return whether a render layer currently has canvas work to paint.
load_screenshot
pub fn load_screenshot(&self, id: u32, data: ScreenshotData) -> bool
Store a screenshot payload delivered by the chassis.
new
pub fn new(globals: Globals) -> Self
Create a runtime context for normal app execution.
register_layer_scroller_owner
pub fn register_layer_scroller_owner(&self, layer_id: usize, scroller_id: ExpandedNodeIdentifier)
Record that a render layer is owned by a particular scroller.
register_root_expanded_node
pub fn register_root_expanded_node(&self, root: &Rc<ExpandedNode>)
Store the root expanded node after it has been initialized.
release_touch_target
pub fn release_touch_target(&self, identifier: i64) -> Option<Rc<ExpandedNode>>
Release and resolve the node captured for a completed touch sequence.
remove_from_cache
pub fn remove_from_cache(&self, node: &Rc<ExpandedNode>)
Remove a node from runtime lookup caches.
remove_scroller_surface_state
pub fn remove_scroller_surface_state(&self, id: u32)
Remove cached scroller surface state.
resize_canvas_layers_to
pub fn resize_canvas_layers_to(&self, id: usize)
Ensure the dirty-canvas table has entries up to the requested layer count.
set_canvas_dirty
pub fn set_canvas_dirty(&self, id: usize)
Mark a canvas layer dirty.
set_canvas_drawable_layers
pub fn set_canvas_drawable_layers(&self, layers: HashSet<usize>)
Replace the set of render layers that currently contain canvas drawables.
set_root_scroller_id
pub fn set_root_scroller_id(&self, id: Option<u32>)
Mark which node currently delegates root scrolling behavior to the page.
set_scroller_surface_state
pub fn set_scroller_surface_state(&self, id: u32, state: ScrollerSurfaceState) -> ScrollerSurfaceStateChange
Remember browser-owned scroller state for native compositing and scroll transforms.
set_visual_viewport_state
pub fn set_visual_viewport_state(&self, state: VisualViewportState)
Cache the browser visual viewport state for root scroller math.
update_scroller_surface_scroll
pub fn update_scroller_surface_scroll(&self, id: u32, scroll_x: f64, scroll_y: f64, presentation_scroll_x: f64, presentation_scroll_y: f64) -> ScrollerSurfaceStateChange
Update hot scroll offsets for an existing native scroller surface without touching structural state.
RuntimePropertiesStackFrame
Data structure for a single frame of our runtime stack, including
a reference to its parent frame and properties for
runtime evaluation, e.g. of Expressions. RuntimePropertiesStackFrames also track
timeline playhead position.
Components push RuntimePropertiesStackFrames before computing properties and pop them after computing, thus providing a
hierarchical store of node-relevant data that can be bound to symbols in expressions.
ScrollerSurfaceState
Last-known scroll state for a native or browser-owned scroller surface.
Properties
viewport_width
Type: f64
viewport_height
Type: f64
content_width
Type: f64
content_height
Type: f64
scroll_x
Type: f64
scroll_y
Type: f64
presentation_scroll_x
Type: f64
presentation_scroll_y
Type: f64
clip_content
Type: bool
VisualViewportState
Browser visual viewport state used when page scrolling participates in root scroller behavior.
Properties
width
Type: f64
height
Type: f64
offset_x
Type: f64
offset_y
Type: f64
page_scroll_x
Type: f64
page_scroll_y
Type: f64
Enums
ScrollerSurfaceStateChange
Coarse classification of changes to a native or browser-owned scroller surface.
Variants
Unchanged
ScrollOnly
Structural
rendering
Structs
BaseInstance
Shared storage carried by every concrete InstanceNode.
Properties
handler_registry
Type: Option<Rc<RefCell<HandlerRegistry>>>
instance_prototypical_properties
Type: PropertiesInit
instance_prototypical_common_properties
Type: CommonPropertiesInit
component_settings
Type: Option<Vec<SettingsBlockElement>>
template_node_identifier
Type: Option<UniqueTemplateNodeIdentifier>
template_node_type_id
Type: Option<TypeId>
template_node_selector_info
Type: Option<TemplateNodeSelectorInfo>
transition_config
Type: ComponentTransitionConfig
properties_scope
Type: PropertiesScopeInit
Implementations
flags
pub fn flags(&self) -> &InstanceFlags
Static behavior flags for this instance.
get_handler_registry
pub fn get_handler_registry(&self) -> Option<Rc<RefCell<HandlerRegistry>>>
Returns a handle to a node-managed HandlerRegistry, a mapping between event types and handlers.
Each node that can handle events is responsible for implementing this; Component instances generate
the necessary code to wire up userland events like <SomeNode @click=self.handler>. Primitives must handle
this explicitly, see e.g. [pax_std::drawing::rectangle::RectangleInstance#get_handler_registry].
get_instance_children
pub fn get_instance_children(&self) -> &InstanceNodePtrList
Return the list of instance nodes that are children of this one. Intuitively, this returns the nodes owned directly by this instance's definition.
For Components, this returns the root(s) of the component template, not the
projected children supplied by the containing component.
new
pub fn new(args: InstantiationArgs, flags: InstanceFlags) -> Self
Build shared instance state from compiler-generated instantiation args.
InstanceFlags
Static traversal/rendering flags for a concrete instance node.
Properties
invisible_to_slot
Type: bool
Used for exotic tree traversals for Slot, e.g. for Stacker > Repeat > Rectangle
where the repeated Rectangles need to be be considered direct children of Stacker.
Repeat and Conditional set this true so their active children can be
considered direct projected children by slot-driven containers.
invisible_to_raycasting
Type: bool
Certain elements, such as Groups and Components, are invisible to ray-casting. Since these container elements are on top of the elements they contain, this is needed otherwise the containers would intercept rays that should hit their contents.
layer
Type: Layer
The layer type (Layer::Native, Layer::NativeNonOccluding, or Layer::Canvas)
for this RenderNode.
Default is Layer::Canvas, and must be overwritten for InstanceNodes that manage native
content.
is_component
Type: bool
Only true for ComponentInstance
is_slot
Type: bool
Is this node a Slot?
InstantiationArgs
Construction payload used when compiler-generated code instantiates an InstanceNode.
Properties
prototypical_common_properties
Type: CommonPropertiesInit
prototypical_properties
Type: PropertiesInit
handler_registry
Type: Option<Rc<RefCell<HandlerRegistry>>>
children
Type: Option<InstanceNodePtrList>
component_template
Type: Option<InstanceNodePtrList>
component_settings
Type: Option<Vec<SettingsBlockElement>>
template_node_identifier
Type: Option<UniqueTemplateNodeIdentifier>
template_node_type_id
Type: Option<TypeId>
template_node_selector_info
Type: Option<TemplateNodeSelectorInfo>
transition_config
Type: ComponentTransitionConfig
properties_scope
Type: PropertiesScopeInit
ReusableInstanceNodeArgs
Lightweight clone of reusable base-node data for helper constructors.
Properties
handler_registry
Type: Option<Rc<RefCell<HandlerRegistry>>>
children
Type: InstanceNodePtrList
template_node_identifier
Type: Option<UniqueTemplateNodeIdentifier>
template_node_type_id
Type: Option<TypeId>
template_node_selector_info
Type: Option<TemplateNodeSelectorInfo>
Implementations
new
pub fn new(base: &BaseInstance) -> Self
Capture the reusable portions of a BaseInstance.
StrokeInstance
Resolved stroke style used by canvas drawing primitives.
Properties
color
Type: Color
width
Type: f64
style
Type: StrokeStyle
Enums
CommonPropertiesInit
Structured initialization for node-local common properties.
Variants
Default
Inline { defined_properties: BTreeMap<String, ValueDefinition> }
Template(Rc<TemplatePropertyPlan>)
Factory(CommonPropertiesFactory)
NodeType
Coarse runtime category for an instance node.
Variants
Component
Primitive
PropertiesInit
Structured initialization for node-local typed properties.
Variants
DescriptorDefault(&'static ErasedComponentDescriptor)
DescriptorInline { descriptor: &'static ErasedComponentDescriptor, defined_properties: BTreeMap<String, ValueDefinition> }
Template { descriptor: &'static ErasedComponentDescriptor, plan: Rc<TemplatePropertyPlan> }
Factory(PropertiesFactory)
PropertiesScopeInit
How an expanded node should expose component-local symbols into scope.
Variants
None
Descriptor(&'static ErasedComponentDescriptor)
Factory(PropertiesScopeFactory)
Traits
InstanceNode
Central runtime representation of a properties-computable and renderable node.
InstanceNodes are conceptually stateless, and rely on [ExpandedNode]s for stateful representations.
An InstanceNode sits in between a pax_compiler::TemplateNodeDefinition, the
compile-time definition analogue to this instance, and [ExpandedNode].
There is a 1:1 relationship between pax_compiler::TemplateNodeDefinitions and InstanceNodes.
There is a one-to-many relationship between one InstanceNode and possibly many variant [ExpandedNode]s,
due to duplication via for.
InstanceNodes are architecturally "type-aware" — they can perform type-specific operations e.g. on the state stored in [ExpandedNode], while
[ExpandedNode]s are "type-blind". The latter store polymorphic data but cannot operate on it without the type-aware assistance of their linked InstanceNode.
(See RepeatInstance::expand_node where we visit a singular InstanceNode several times, producing multiple [ExpandedNode]s.)
Type Aliases
InstanceNodePtr
Type aliases to make it easier to work with nested Rcs and RefCells for instance nodes.
repeat
Structs
RepeatInstance
A special "control-flow" primitive associated with the for statement.
Repeat allows for nodes to be rendered dynamically per data specified in source_expression.
That is: for a source_expression of length n, Repeat will render its
template n times, each with an embedded component context (RepeatItem)
with an index i and a pointer to that relevant datum source_expression[i]
Properties
base
Type: BaseInstance
RepeatItem
Per-iteration bindings exposed inside a for template body.
Properties
elem
i
Type: Property<usize>
RepeatProperties
Contains modal vec and range variants, describing whether the Repeat source
is encoded as a Vec<T> (where T is a PaxValue properties type) or as a Range<isize>
Properties
source_expression
iterator_i_symbol
Type: Property<Option<String>>
iterator_elem_symbol
Type: Property<Option<String>>
repeat_key_expression
Type: Option<ExpressionInfo>
router
Runtime routing primitives used by declarative Router / Route control flow.
Structs
CompiledRouteBranch
Compiled route branch used by the runtime matcher.
Properties
default
Type: bool
Whether this branch is the fallback default=true branch.
modal
Type: bool
Whether this branch should stack over the previously active branch.
RouteLocation
Structured application location shared across platforms.
On web targets this is serialized to and from window.location, while on
non-web targets it remains a platform-agnostic route state model.
Properties
path_segments
Type: Vec<String>
Decoded path segments, with / represented by an empty vector.
query
Type: HashMap<String, Vec<String>>
Query-string values keyed by parameter name, preserving repeated keys.
fragment
Type: Option<String>
Optional fragment without the leading #.
Implementations
root
pub fn root() -> Self
Returns the canonical root location.
with_path_segments
pub fn with_path_segments(&self, path_segments: Vec<String>) -> Self
Clones this location while replacing only the path segments.
RouteMatch
Structured match data exposed to the active route subtree as route.
Properties
location
Type: RouteLocation
Location as seen by the current router scope.
global_location
Type: RouteLocation
Full global application location.
params
Type: HashMap<String, String>
Named values captured from :param segments.
consumed_segments
Type: usize
Number of path segments consumed by the selected branch.
remainder
Type: Vec<String>
Remaining path tail left after the selected branch.
is_exact
Type: bool
Whether the selected branch consumed the entire scoped path.
Implementations
remainder_location
pub fn remainder_location(&self) -> RouteLocation
Converts the remainder into a RouteLocation for nested router input.
RouterInstance
Implementations
instantiate_with_branches
pub fn instantiate_with_branches(args: InstantiationArgs, branches: Vec<CompiledRouteBranch>, branch_child_ranges: Vec<Range<usize>>) -> Rc<Self>
Creates a router instance with precompiled route branches.
RouterProperties
Internal router inputs carried into a RouterInstance.
Properties
input_location
Type: Property<RouteLocation>
Location scoped to the current router.
global_location
Type: Property<RouteLocation>
Full application location retained for diagnostics and coordination.
Constants
INTERNAL_ROUTE_LOCATION_SYMBOL
Internal stack symbol carrying the router input location for nested scopes.
INTERNAL_ROUTE_MATCH_SYMBOL
Internal stack symbol carrying the current route match object.
slot
Structs
Slot
Contains the index value for slot, either a literal or an expression.
Properties
index
is_remainder
Type: Property<bool>
last_node_id
Type: Property<usize>
showing_node
Type: Property<Weak<ExpandedNode>>
SlotInstance
A special "control-flow" primitive (a la yield or perhaps goto) that
renders projected payload into a node's encapsulated implementation.
Slot relies on raw projected_children being present on the runtime stack
and will not render any content if there are none. Projection is the engine
transport mechanism; semantic container logic should usually reason in terms
of received_children instead.
Consider a Stacker: the owner of a Stacker passes the Stacker some nodes to render
inside the cells of the Stacker. To the owner of the Stacker, those nodes might seem like
received children. Inside Stacker's encapsulated implementation, those
received children travel as projected children until Slot becomes their
rendered home. This same technique is portable and applicable elsewhere via
Slot.
pax-manifest
Submodules
Structs
ComponentDefinition
Container for an entire component definition — includes template, settings, event bindings, property definitions, and compiler + reflection metadata
Properties
type_id
Type: TypeId
is_main_component
Type: bool
is_primitive
Type: bool
is_struct_only_component
Type: bool
Flag describing whether this component definition is a "struct-only component", a
struct decorated with #[pax] for use as the T in Property<T>.
module_path
Type: String
primitive_instance_import_path
Type: Option<String>
For primitives like Rectangle or Group, a separate import path is required for the Instance (render context) struct and the Definition struct. For primitives, then, we need to store an additional import path to use when instantiating.
template
Type: Option<ComponentTemplate>
settings
Type: Option<Vec<SettingsBlockElement>>
timelines
Type: Vec<TimelineDefinition>
route_branch
Type: Option<RouteBranchDescriptor>
ComponentTemplate
Implementations
from_parts
pub fn from_parts(containing_component: TypeId, root: VecDeque<TemplateNodeId>, children: HashMap<TemplateNodeId, VecDeque<TemplateNodeId>>, nodes: HashMap<TemplateNodeId, TemplateNodeDefinition>, next_id: usize, template_source_file_path: Option<String>) -> Self
Construct a component template from already-materialized storage.
ControlFlowRouteBranchDefinition
Properties
metadata
Type: Option<RouteMetadataDefinition>
Compiler-only document metadata attached to this declarative route.
The web compiler consumes this before cartridge generation. Runtime routing and baked program representations intentionally omit it.
ExpressionCompilationInfo
Properties
dependencies
Type: Vec<String>
symbols used in the expression
GradientDefinition
Compile-time representation of an inline @gradient value.
Properties
shape
Type: GradientShapeDefinition
elements
Type: Vec<GradientElement>
Implementations
stops
pub fn stops(&self) -> impl Iterator
Iterate only stop entries, skipping comments.
GradientStopDefinition
A single color stop in a gradient ramp.
Properties
position
Type: Size
color
Type: ValueDefinition
HostCrateInfo
Pulled from host Cargo.toml
Properties
name
Type: String
for example: pax-example
identifier
Type: String
for example: pax_example
import_prefix
Type: String
for example: some_crate::pax_reexports,
LiteralBlockDefinition
Container for a parsed Literal object
Properties
explicit_type_pascal_identifier
Type: Option<Token>
elements
Type: Vec<SettingElement>
Implementations
new
pub fn new(elements: Vec<SettingElement>) -> Self
Construct a literal object block from setting elements.
get_all_settings
pub fn get_all_settings<'a>(&'a self) -> Vec<(&'a Token, &'a ValueDefinition)>
Return only actual setting entries, omitting comments.
LocationInfo
Container for holding metadata about original Location in Pax Template Used for source-mapping
Properties
start_line_col
Type: (usize, usize)
end_line_col
Type: (usize, usize)
NodeLocation
Full editable location metadata for a template node.
Properties
type_id
Type: TypeId
tree_location
Type: TreeLocation
index
Type: TreeIndexPosition
Implementations
get_tree_location
pub fn get_tree_location(&self) -> &TreeLocation
Parent-location component of this node location.
get_type_id
pub fn get_type_id(&self) -> &TypeId
Type id of the node at this location.
new
pub fn new(type_id: TypeId, location: TreeLocation, index: TreeIndexPosition) -> Self
Construct a node location.
PaxManifest
Definition container for an entire Pax cartridge
Properties
components
Type: BTreeMap<TypeId, ComponentDefinition>
main_component_type_id
Type: TypeId
type_table
Type: TypeTable
assets_dirs
Type: Vec<String>
Compiler metadata: list of fully qualified asset directories, gathered during compiletime, from which assets will be copied for bundling into executable binaries
engine_import_path
Type: String
Compiler metadata: the import prefix for the engine module, pax_kit::pax_engine by default,
but parameterizable for crates such as pax-std that integrate with pax-engine directly.
PropertyDefinition
Properties
name
Type: String
String representation of the symbolic identifier of a declared Property
flags
Type: PropertyDefinitionFlags
Flags, used ultimately by ExpressionSpecInvocations, to denote
e.g. whether a property is the i or elem of a Repeat, which allows
for special-handling the RIL that invokes these values
type_id
Type: TypeId
Statically known type_id for this Property's associated TypeDefinition
Implementations
Describes static metadata surrounding a property, for example
the string representation of the property's name and a TypeInfo
entry for the property's statically discovered type
primitive_with_name
pub fn primitive_with_name(type_name: &str, symbol_name: &str) -> Self
Shorthand factory / constructor
PropertyDefinitionFlags
These flags describe the aspects of properties that affect RIL codegen. Properties are divided into modal axes (exactly one value should be true per axis per struct instance) Codegen considers each element of the cartesian product of these axes
Properties
is_binding_repeat_i
Type: bool
Does this property represent the index i in for (elem, i) ?
is_binding_repeat_elem
Type: bool
Does this property represent elem in for (elem, i) OR for elem in 0..5 ?
is_repeat_source_range
Type: bool
Is the source being iterated over a Range?
is_repeat_source_iterable
Type: bool
Is the source being iterated over an iterable, like Vec<T>?
is_property_wrapped
Type: bool
Describes whether this property is a Property-wrapped T in Property<T>
This distinction affects our ability to dirty-watch a particular property, and
has implications on codegen
is_enum
Type: bool
Describes whether this property is an enum variant property
RouteBranchDescriptor
Compile-time contract allowing a component or primitive to act as a direct
route branch child of Router.
Properties
path_property
Type: String
default_property
Type: String
modal
Type: bool
SettingsConditionalBlock
Top-level conditional content inside a settings block.
Properties
branches
Type: Vec<SettingsConditionalBranch>
SettingsConditionalBranch
One branch inside a settings conditional. None represents else.
Properties
condition_expression
Type: Option<ExpressionInfo>
elements
Type: Vec<SettingsBlockElement>
TemplateNodeDefinition
Represents an entry within a component template, e.g. a <Rectangle> declaration inside a template
Each node in a template is represented by exactly one TemplateNodeDefinition, and this is a compile-time
concern. Note the difference between compile-time definitions and runtime instances.
A compile-time TemplateNodeDefinition corresponds to a single runtime RenderNode instance.
Properties
type_id
Type: TypeId
Reference to the unique string ID for a component, e.g. primitive::Frame or component::Stacker
control_flow_settings
Type: Option<ControlFlowSettingsDefinition>
Iff this TND is a control-flow node: parsed control flow attributes (slot/if/for)
settings
Type: Option<Vec<SettingElement>>
IFF this TND is NOT a control-flow node: parsed key-value store of attribute definitions (like some_key="some_value")
selector_info
Type: TemplateNodeSelectorInfo
Normalized selector metadata preserved for runtime/designtime matching.
raw_comment_string
Type: Option<String>
IFF this TND is a comment node: raw comment string
TemplateNodeId
Stable id for a template node inside a single component.
Implementations
as_usize
pub fn as_usize(&self) -> usize
Numeric index backing this id.
build
pub fn build(id: usize) -> Self
Construct a template node id from its numeric index.
TimelineDefinition
Compile-time representation of a declared timeline.
Properties
name
Type: Option<Token>
playhead
Type: Option<ValueDefinition>
duration
Type: Option<ValueDefinition>
repeat
Type: bool
interruption
Type: InOutInterruption
elements
Type: Vec<TimelineBlockElement>
TimelineKeyframe
A single timeline value at a frame, duration, or percent marker.
Properties
marker
Type: TimelineMarker
value
Type: ValueDefinition
easing
Type: Option<Token>
TimelineSelectorBlockDefinition
Selector body inside a timeline block.
Properties
elements
Type: Vec<TimelineSelectorElement>
TimelineTrackDefinition
Track-level timeline data for one animated property.
Properties
elements
Type: Vec<TimelineTrackElement>
playhead
Type: Option<Box<ValueDefinition>>
duration
Type: Option<Box<ValueDefinition>>
repeat
Type: Option<bool>
starting_value
Type: Option<Box<ValueDefinition>>
interruption
Type: InOutInterruption
use_local_property_scope
Type: bool
Implementations
keyframes
pub fn keyframes(&self) -> impl Iterator
Iterate only keyframe entries, skipping comments.
Token
Container for parsed values with optional location information Location is optional in case this token was generated dynamically
Properties
token_value
Type: String
token_location
Type: Option<LocationInfo>
Implementations
new
pub fn new(token_value: String, token_location: LocationInfo) -> Self
Construct a token with source location information.
new_without_location
pub fn new_without_location(token_value: String) -> Self
Construct a token synthesized without a source location.
TransitionDefinition
Pair of timeline tracks bound to a node's enter/exit lifecycle.
Properties
enter
Type: Option<TimelineTrackDefinition>
exit
Type: Option<TimelineTrackDefinition>
starting_value
Type: Option<Box<ValueDefinition>>
TypeDefinition
Describes metadata surrounding a property's type, gathered from a combination of static & dynamic analysis
Properties
type_id
Type: TypeId
Program-unique ID for this type
inner_iterable_type_id
Type: Option<TypeId>
Statically known type_id for this Property's iterable TypeDefinition, that is,
T for some Property<Vec<T>>
property_definitions
Type: Vec<PropertyDefinition>
A vec of PropertyType, describing known addressable (sub-)properties of this PropertyType
Implementations
builtin_vec_rc_ref_cell_any_properties
pub fn builtin_vec_rc_ref_cell_any_properties(inner_iterable_type_id: TypeId) -> Self
Used by Repeat for source expressions, e.g. the self.some_vec in for elem in self.some_vec
TypeId
Implementations
build_blank_component
pub fn build_blank_component(pascal_identifier: &str) -> Self
Build a typeid for a transient component
build_map
pub fn build_map(key_identifier: &str, value_identifier: &str) -> Self
Build a TypeId for map types like std::collections::HashMap<String><Color>
build_option
pub fn build_option(identifier: &str) -> Self
Build a TypeId for option types like std::option::Option<Color>
build_primitive
pub fn build_primitive(identifier: &str) -> Self
Build a TypeId for rust primitives like u8 or String
build_range
pub fn build_range(identifier: &str) -> Self
Build a TypeId for range types like std::ops::Range<Color>
build_singleton
pub fn build_singleton(import_path: &str, pascal_identifier: Option<&str>) -> Self
Build a TypeId for a most types, like Stacker or SpecialComponent
build_vector
pub fn build_vector(elem_identifier: &str) -> Self
Build a TypeId for vector types like Vec<Color>
UniqueTemplateNodeIdentifier
Globally unique identity for a template node: component type plus local template-node id.
Implementations
build
pub fn build(component: TypeId, template_node_id: TemplateNodeId) -> Self
Construct a globally unique template-node id.
get_containing_component_type_id
pub fn get_containing_component_type_id(&self) -> TypeId
Component that owns this template node.
get_template_node_id
pub fn get_template_node_id(&self) -> TemplateNodeId
Node id within the containing component template.
Enums
ControlFlowConditionalBranchKind
Container for storing parsed control flow information, for example the string (PAXEL) representations of condition / slot / repeat expressions and the related vtable ids (for "punching" during expression compilation)
Variants
If
ElseIf
Else
ControlFlowRepeatPredicateDefinition
Container for holding parsed data describing a Repeat (for)
predicate, for example the (elem, i) in for (elem, i) in foo or
the elem in for elem in foo
Variants
ElemId(String)
ElemIdIndexId(String, String)
GradientElement
One entry inside a gradient block.
Variants
Stop(GradientStopDefinition)
Comment(String)
GradientShapeDefinition
Shape-specific parameters for a gradient. V1 maps directly to runtime Fill variants.
Variants
Linear { start: Option<Box<ValueDefinition>>, end: Option<Box<ValueDefinition>> }
Radial { start: Box<ValueDefinition>, end: Box<ValueDefinition>, radius: Box<ValueDefinition> }
InOutInterruption
Controls how an @in or @out timeline begins when it directly reverses
the other lifecycle transition on the same mounted instance.
Variants
Takeover
Continue from the property's currently sampled value.
Restart
Begin from the destination timeline's authored starting value.
Number
Parsed numeric literal before final type coercion.
Variants
Float(f64)
Int(isize)
PaxType
Manifest-level type identity category.
Variants
If
Router
Slot
Repeat
Comment
BlankComponent
Primitive
Singleton
Range
Option
Vector
Map
Unknown
SettingElement
One key/value or comment entry inside a literal block.
Variants
Setting(Token, ValueDefinition)
Comment(String)
SettingsBlockElement
One entry inside a settings block.
Variants
SelectorBlock(Token, LiteralBlockDefinition)
Handler(Token, Vec<Token>)
Transition(Token, Token)
Conditional(SettingsConditionalBlock)
Comment(String)
TimelineBlockElement
One entry inside a timeline block.
Variants
SelectorBlock(Token, TimelineSelectorBlockDefinition)
Comment(String)
TimelineMarker
Timeline position expressed as an absolute frame, absolute duration, or normalized percentage.
Variants
Frame(u64)
Duration(Duration)
Percent(f64)
TimelineSelectorElement
One selector-scoped element inside a timeline block.
Variants
Track(Token, TimelineTrackDefinition)
Comment(String)
TimelineTrackElement
One entry in a timeline track.
Variants
Keyframe(TimelineKeyframe)
Comment(String)
TreeIndexPosition
Desired insertion position among siblings.
Variants
Top
Bottom
At(usize)
Implementations
get_index
pub fn get_index(&self, len: usize) -> usize
Resolve this symbolic position against a sibling-list length.
new
pub fn new(index: usize) -> Self
Construct an explicit index position.
TreeLocation
Parent relationship for a node inside a component template tree.
Variants
Root
Parent(TemplateNodeId)
Unit
Parsed Pax unit suffix.
Variants
Pixels
Percent
ValueDefinition
Container for settings values, storing all possible variants, populated at parse-time and used at compile-time
Variants
Undefined
LiteralValue(PaxValue)
Block(LiteralBlockDefinition)
Timeline(TimelineTrackDefinition)
Gradient(GradientDefinition)
Transition(TransitionDefinition)
Expression(ExpressionInfo)
(Expression contents, vtable id binding)
Identifier(PaxIdentifier)
(Expression contents, vtable id binding)
DoubleBinding(PaxIdentifier)
(Expression contents, vtable id binding)
EventBindingTarget(PaxIdentifier)
Functions
escape_identifier
pub fn escape_identifier(input: String) -> String
Mangle an identifier into a token-safe representation for generated symbols.
get_common_properties_as_property_definitions
pub fn get_common_properties_as_property_definitions() -> Vec<PropertyDefinition>
Common properties represented as manifest property definitions.
get_common_properties_type_ids
pub fn get_common_properties_type_ids() -> Vec<TypeId>
Type ids for the built-in common properties attached to every template node.
Constants
SUPPORTED_NONNUMERIC_PRIMITIVES
Primitive nonnumeric Rust types supported directly by manifest reflection.
SUPPORTED_NUMERIC_PRIMITIVES
Primitive numeric Rust types supported directly by manifest reflection.
cartridge_generation
Structs
CommonProperty
Common property metadata passed into cartridge codegen templates.
ComponentInfo
Template context for generating a component's cartridge code.
Properties
type_id
Type: TypeId
pascal_identifier
Type: String
symbol_identifier
Type: String
Collision-free Rust helper prefix derived from the complete component type identity.
primitive_instance_import_path
Type: Option<String>
properties
Type: Vec<PropertyInfo>
handlers
Type: Vec<HandlerInfo>
HandlerInfo
Event handler entry passed into cartridge codegen templates.
Properties
name
Type: String
args_type
Type: Option<String>
PropertyInfo
Property entry passed into cartridge codegen templates.
Properties
name
Type: String
property_type
Type: PropertyDefinition
program_ir
Structs
ProgramIR
Runtime-facing semantic program model derived from a rich PaxManifest.
ProgramIR is a transitional intermediate representation: it preserves the
semantic program structure while stripping obviously source-only and
compiler-only manifest data. Debug/designtime flows may continue to mount
rich manifests directly until later phases cut execution over to ProgramIR.
Properties
components
Type: BTreeMap<TypeId, ProgramComponent>
main_component_type_id
Type: TypeId
type_table
Type: BTreeMap<TypeId, TypeDefinition>
assets_dirs
Type: Vec<String>
selectors
Structs
TemplateNodeSelectorInfo
Selector identity persisted for one authored template node.
class_binding is the sole class representation: literal strings and lists
as well as PAXEL expressions all cross the manifest/runtime boundary here.
Properties
source_location
Type: Option<LocationInfo>
Source span of the authored node, when available.
id
Type: Option<Token>
The node's authored id selector.
class_binding
Type: Option<ValueDefinition>
The complete class attribute value. Literal and expression-backed classes share this
representation so runtime selector resolution has a single source of truth.
Implementations
literal_classes
pub fn literal_classes(&self) -> Vec<String>
Return class names that can be read without evaluating an expression.
pax-message
Submodules
Structs
AccelInterruptArgs
Device acceleration payload, in meters per second squared.
Properties
x
Type: f64
y
Type: f64
z
Type: f64
AddedLayerArgs
Chassis acknowledgement that render layers were added.
Properties
num_layers_added
Type: u32
layer_id
Type: Option<u32>
AnyCreatePatch
Common creation payload shared by native element families.
Properties
id
Type: u32
parent_frame
Type: Option<u32>
render_layer_id
Type: u32
AppleLiquidGlassPatch
Apple-specific native liquid-glass effect payload.
Properties
group_id
Type: u32
spacing
Type: f64
interactive
Type: bool
tint
Type: Option<ColorMessage>
variant
Type: String
BrowserConfigInterruptArgs
Browser capability flags discovered by the web chassis.
Properties
allow_scroller_vector_layers
Type: bool
allow_nested_scroller_vector_layers
Type: bool
ButtonPatch
Create/update patch for a native button.
Properties
id
Type: u32
parent_frame
Type: Option<Option<u32>>
z_index
Type: Option<i32>
hover_color
Type: Option<ColorMessage>
outline_stroke_color
Type: Option<ColorMessage>
outline_stroke_width
Type: Option<f64>
corner_radius
Type: Option<f64>
transform
Type: Option<Vec<f64>>
size_x
Type: Option<f64>
size_y
Type: Option<f64>
opacity
Type: Option<f64>
content
Type: Option<String>
color
Type: Option<ColorMessage>
style
Type: Option<TextStyleMessage>
liquid_glass
Type: Option<Option<AppleLiquidGlassPatch>>
ChassisResizeRequestArgs
Chassis response containing measured native control bounds.
Properties
id
Type: u32
width
Type: f64
height
Type: f64
CheckboxPatch
Create/update patch for a native checkbox.
Properties
id
Type: u32
parent_frame
Type: Option<Option<u32>>
z_index
Type: Option<i32>
background
Type: Option<ColorMessage>
background_checked
Type: Option<ColorMessage>
outline_color
Type: Option<ColorMessage>
outline_width
Type: Option<f64>
corner_radius
Type: Option<f64>
transform
Type: Option<Vec<f64>>
size_x
Type: Option<f64>
size_y
Type: Option<f64>
opacity
Type: Option<f64>
checked
Type: Option<bool>
liquid_glass
Type: Option<Option<AppleLiquidGlassPatch>>
CheckboxStyleMessage
Style payload shared by checkbox-like controls.
ClickInterruptArgs
Click interrupt payload.
Properties
x
Type: f64
y
Type: f64
button
Type: MouseButtonMessage
modifiers
Type: Vec<ModifierKeyMessage>
ContextMenuInterruptArgs
Context-menu interrupt payload.
Properties
x
Type: f64
y
Type: f64
button
Type: MouseButtonMessage
modifiers
Type: Vec<ModifierKeyMessage>
DoubleClickInterruptArgs
Double-click interrupt payload.
Properties
x
Type: f64
y
Type: f64
button
Type: MouseButtonMessage
modifiers
Type: Vec<ModifierKeyMessage>
DropFileArgs
File-drop interrupt payload.
Properties
x
Type: f64
y
Type: f64
name
Type: String
mime_type
Type: String
size
Type: u64
DropdownPatch
Create/update patch for a native dropdown/select element.
Properties
id
Type: u32
parent_frame
Type: Option<Option<u32>>
z_index
Type: Option<i32>
selected_id
Type: Option<u32>
options
Type: Option<Vec<String>>
transform
Type: Option<Vec<f64>>
size_x
Type: Option<f64>
size_y
Type: Option<f64>
opacity
Type: Option<f64>
background
Type: Option<ColorMessage>
stroke_color
Type: Option<ColorMessage>
stroke_width
Type: Option<f64>
corner_radius
Type: Option<f64>
style
Type: Option<TextStyleMessage>
liquid_glass
Type: Option<Option<AppleLiquidGlassPatch>>
EventBlockerPatch
Create/update patch for a native hit-test blocker with an optional solid background.
Properties
id
Type: u32
parent_frame
Type: Option<Option<u32>>
z_index
Type: Option<i32>
size_x
Type: Option<f64>
size_y
Type: Option<f64>
transform
Type: Option<Vec<f64>>
opacity
Type: Option<f64>
background
Type: Option<ColorMessage>
FocusInterruptArgs
Focus interrupt marker payload.
FormButtonClickArgs
Native button click payload.
Properties
id
Type: u32
FormCheckboxToggleArgs
Checkbox state-change interrupt payload.
Properties
state
Type: bool
id
Type: u32
FormDropdownChangeArgs
Dropdown selection-change payload.
Properties
id
Type: u32
selected_id
Type: u32
FormRadioListChangeArgs
Radio-list selection-change payload.
Properties
id
Type: u32
selected_id
Type: u32
FormSliderChangeArgs
Slider value-change payload.
Properties
id
Type: u32
value
Type: f64
FormTextboxChangeArgs
Textbox committed-value change payload.
Properties
text
Type: String
id
Type: u32
FormTextboxInputArgs
Textbox in-progress input payload.
Properties
text
Type: String
id
Type: u32
FramePatch
Create/update patch for a native frame host.
Properties
id
Type: u32
parent_frame
Type: Option<Option<u32>>
z_index
Type: Option<i32>
clip_content
Type: Option<bool>
corner_radius
Type: Option<f64>
size_x
Type: Option<f64>
size_y
Type: Option<f64>
transform
Type: Option<Vec<f64>>
clip_path
Type: Option<String>
opacity
Type: Option<f64>
presented_bounds
Type: Option<[f64; 4]>
presented_clip_bounds
Type: Option<[f64; 4]>
GlassSurfacePatch
Create/update patch for a materialized native liquid-glass surface.
Properties
id
Type: u32
parent_frame
Type: Option<Option<u32>>
z_index
Type: Option<i32>
transform
Type: Option<Vec<f64>>
size_x
Type: Option<f64>
size_y
Type: Option<f64>
opacity
Type: Option<f64>
corner_radius
Type: Option<f64>
liquid_glass
Type: Option<AppleLiquidGlassPatch>
GyroInterruptArgs
Device orientation payload, in degrees.
Properties
x
Type: f64
y
Type: f64
z
Type: f64
ImageDataArgs
Image-load payload carrying image metadata without a byte pointer.
Properties
id
Type: u32
path
Type: String
width
Type: usize
height
Type: usize
ImagePatch
Image-load request sent to the chassis.
Properties
id
Type: u32
path
Type: Option<String>
ImagePointerArgs
Image-load payload carrying a pointer to chassis-owned image bytes.
Properties
id
Type: u32
path
Type: String
image_data
Type: u64
image_data_length
Type: usize
width
Type: usize
height
Type: usize
InterruptBuffer
Raw FFI buffer containing serialized interrupts.
Properties
data_ptr
Type: *const u8
length
Type: u64
KeyDownInterruptArgs
Key-down interrupt payload.
Properties
key
Type: String
modifiers
Type: Vec<ModifierKeyMessage>
is_repeat
Type: bool
KeyPressInterruptArgs
Key-press interrupt payload.
Properties
key
Type: String
modifiers
Type: Vec<ModifierKeyMessage>
is_repeat
Type: bool
KeyUpInterruptArgs
Key-up interrupt payload.
Properties
key
Type: String
modifiers
Type: Vec<ModifierKeyMessage>
is_repeat
Type: bool
LayerSurfaceScreenshotData
Completed screenshot bytes for one physical surface within a logical canvas layer.
Properties
id
Type: u32
key
Type: String
data
Type: Vec<u8>
width
Type: usize
height
Type: usize
origin_x
Type: f32
origin_y
Type: f32
logical_width
Type: f32
logical_height
Type: f32
LinkStyleMessage
Serializable style payload for link text.
Properties
font
Type: Option<FontPatch>
fill
Type: Option<ColorMessage>
underline
Type: Option<bool>
size
Type: Option<f64>
LocalFontMessage
Local bundled font payload.
Properties
family
Type: Option<String>
path
Type: Option<String>
style
Type: Option<FontStyleMessage>
weight
Type: Option<FontWeightMessage>
MaskPathPatch
One vector coverage path entry for a native occlusion mask.
Properties
path
Type: String
clips
Type: Vec<String>
opacity
Type: Option<f64>
MessageQueue
Serializable batch of native messages emitted for one runtime tick.
Properties
messages
Type: Vec<NativeMessage>
MouseDownInterruptArgs
Mouse-down interrupt payload.
Properties
x
Type: f64
y
Type: f64
button
Type: MouseButtonMessage
modifiers
Type: Vec<ModifierKeyMessage>
MouseMoveInterruptArgs
Mouse-move interrupt payload.
Properties
x
Type: f64
y
Type: f64
button
Type: MouseButtonMessage
modifiers
Type: Vec<ModifierKeyMessage>
MouseOutInterruptArgs
Mouse-out interrupt payload.
Properties
x
Type: f64
y
Type: f64
button
Type: MouseButtonMessage
modifiers
Type: Vec<ModifierKeyMessage>
MouseOverInterruptArgs
Mouse-over interrupt payload.
Properties
x
Type: f64
y
Type: f64
button
Type: MouseButtonMessage
modifiers
Type: Vec<ModifierKeyMessage>
MouseUpInterruptArgs
Mouse-up interrupt payload.
Properties
x
Type: f64
y
Type: f64
button
Type: MouseButtonMessage
modifiers
Type: Vec<ModifierKeyMessage>
NativeImagePatch
Create/update patch for a native image element.
Properties
id
Type: u32
parent_frame
Type: Option<Option<u32>>
z_index
Type: Option<i32>
transform
Type: Option<Vec<f64>>
size_x
Type: Option<f64>
size_y
Type: Option<f64>
opacity
Type: Option<f64>
url
Type: Option<String>
fit
Type: Option<String>
NativeMaskPatch
Create/update patch for a native occlusion mask surface.
Properties
id
Type: u32
size_x
Type: f64
size_y
Type: f64
entries
Type: Vec<MaskPathPatch>
NativeMessageQueue
Raw FFI buffer containing serialized native messages.
Properties
data_ptr
Type: *mut [u8]
length
Type: u64
NavigationPatch
Request for the chassis to navigate to a URL.
Properties
url
Type: String
target
Type: String
PhotoPickerAssetArgs
Selected image metadata and optional copied bytes from a native photo picker.
Properties
temp_id
Type: String
file_name
Type: Option<String>
mime_type
Type: String
byte_size
Type: u64
width
Type: Option<u32>
height
Type: Option<u32>
source_kind
Type: String
handle
Type: Option<String>
data
Type: Vec<u8>
PhotoPickerInterruptArgs
Native photo picker completion payload.
Properties
id
Type: u32
request_id
Type: u64
status
Type: String
message
Type: Option<String>
photos
Type: Vec<PhotoPickerAssetArgs>
PhotoPickerPatch
Create/update patch for a transparent native photo picker hit target.
Properties
id
Type: u32
parent_frame
Type: Option<Option<u32>>
z_index
Type: Option<i32>
transform
Type: Option<Vec<f64>>
size_x
Type: Option<f64>
size_y
Type: Option<f64>
opacity
Type: Option<f64>
trigger
Type: Option<u64>
source
Type: Option<String>
allow_multiple
Type: Option<bool>
accept
Type: Option<String>
include_bytes
Type: Option<bool>
max_bytes_per_photo
Type: Option<u64>
RadioListPatch
Create/update patch for a native radio list.
Properties
id
Type: u32
parent_frame
Type: Option<Option<u32>>
z_index
Type: Option<i32>
selected_id
Type: Option<u32>
options
Type: Option<Vec<String>>
style
Type: Option<TextStyleMessage>
background_checked
Type: Option<ColorMessage>
outline_color
Type: Option<ColorMessage>
outline_width
Type: Option<f64>
background
Type: Option<ColorMessage>
transform
Type: Option<Vec<f64>>
size_x
Type: Option<f64>
size_y
Type: Option<f64>
opacity
Type: Option<f64>
liquid_glass
Type: Option<Option<AppleLiquidGlassPatch>>
RenderSurfaceUpdateArgs
Browser notification that a retained render surface must be reconfigured.
Properties
layer_id
Type: Option<u32>
RouteChangeInterruptArgs
Canonical route-location payload pushed from chassis state into the runtime.
Properties
path_segments
Type: Vec<String>
query
Type: HashMap<String, Vec<String>>
fragment
Type: Option<String>
ScreenshotData
Completed screenshot bytes returned by the chassis.
Properties
id
Type: u32
data
Type: Vec<u8>
width
Type: usize
height
Type: usize
ScreenshotPatch
Request to capture a screenshot from the chassis.
Properties
id
Type: u32
scale
Type: Option<f64>
ScrollInterruptArgs
Location-targeted scroll delta payload.
Properties
x
Type: f64
y
Type: f64
delta_x
Type: f64
delta_y
Type: f64
ScrollerPatch
Create/update patch for a native/browser-owned scroller.
Properties
id
Type: u32
parent_frame
Type: Option<Option<u32>>
z_index
Type: Option<i32>
transform
Type: Option<Vec<f64>>
size_x
Type: Option<f64>
size_y
Type: Option<f64>
opacity
Type: Option<f64>
clip_content
Type: Option<bool>
corner_radius
Type: Option<f64>
size_inner_pane_x
Type: Option<f64>
size_inner_pane_y
Type: Option<f64>
snap_points_x
Type: Option<Vec<f64>>
snap_points_y
Type: Option<Vec<f64>>
scroll_x
Type: Option<f64>
scroll_y
Type: Option<f64>
presentation_scroll_x
Type: Option<f64>
presentation_scroll_y
Type: Option<f64>
scroll_enabled_x
Type: Option<bool>
scroll_enabled_y
Type: Option<bool>
content_layer_id
Type: Option<u32>
presented_bounds
Type: Option<[f64; 4]>
presented_clip_bounds
Type: Option<[f64; 4]>
subtree_depth
Type: u32
ScrollerPositionInterruptArgs
Native scroller position payload, including optional presentation offsets.
Properties
id
Type: u32
scroll_x
Type: f64
scroll_y
Type: f64
presentation_scroll_x
Type: Option<f64>
presentation_scroll_y
Type: Option<f64>
SelectStartArgs
Selection-start interrupt marker payload.
SetCursorPatch
Request for the chassis to update cursor style.
Properties
cursor
Type: String
SliderPatch
Create/update patch for a native slider.
Properties
id
Type: u32
parent_frame
Type: Option<Option<u32>>
z_index
Type: Option<i32>
value
Type: Option<f64>
step
Type: Option<f64>
min
Type: Option<f64>
max
Type: Option<f64>
transform
Type: Option<Vec<f64>>
size_x
Type: Option<f64>
size_y
Type: Option<f64>
opacity
Type: Option<f64>
accent
Type: Option<ColorMessage>
background
Type: Option<ColorMessage>
corner_radius
Type: Option<f64>
liquid_glass
Type: Option<Option<AppleLiquidGlassPatch>>
SystemFontMessage
System font family payload.
Properties
family
Type: Option<String>
style
Type: Option<FontStyleMessage>
weight
Type: Option<FontWeightMessage>
TapInterruptArgs
Single-touch tap payload normalized to window coordinates.
Properties
x
Type: f64
y
Type: f64
TextInputArgs
Raw text input payload delivered by a native text control.
Properties
text
Type: String
id
Type: u32
TextMeasurementResponseArgs
Chassis response to an engine-authored native text measurement request.
Properties
id
Type: u32
generation
Type: u64
width
Type: f64
height
Type: f64
TextPatch
Create/update patch for native or browser-managed text.
Properties
id
Type: u32
parent_frame
Type: Option<Option<u32>>
z_index
Type: Option<i32>
content
Type: Option<String>
editable
Type: Option<bool>
selectable
Type: Option<bool>
clip
Type: Option<bool>
markdown
Type: Option<bool>
wrap
Type: Option<bool>
transform
Type: Option<Vec<f64>>
size_x
Type: Option<f64>
size_y
Type: Option<f64>
opacity
Type: Option<f64>
style
Type: Option<TextStyleMessage>
style_link
Type: Option<TextStyleMessage>
measure_generation
Type: Option<u64>
TextStyleMessage
Serializable text style payload shared with chassis text renderers.
Properties
font
Type: Option<FontPatch>
font_size
Type: Option<f64>
fill
Type: Option<ColorMessage>
underline
Type: Option<bool>
align_multiline
Type: Option<TextAlignHorizontalMessage>
align_vertical
Type: Option<TextAlignVerticalMessage>
align_horizontal
Type: Option<TextAlignHorizontalMessage>
TextboxPatch
Create/update patch for a native textbox.
Properties
id
Type: u32
parent_frame
Type: Option<Option<u32>>
z_index
Type: Option<i32>
transform
Type: Option<Vec<f64>>
size_x
Type: Option<f64>
size_y
Type: Option<f64>
opacity
Type: Option<f64>
text
Type: Option<String>
background
Type: Option<ColorMessage>
stroke_color
Type: Option<ColorMessage>
stroke_width
Type: Option<f64>
corner_radius
Type: Option<f64>
style
Type: Option<TextStyleMessage>
focus_on_mount
Type: Option<bool>
placeholder
Type: Option<String>
outline_color
Type: Option<ColorMessage>
outline_width
Type: Option<f64>
is_text_area
Type: Option<bool>
liquid_glass
Type: Option<Option<AppleLiquidGlassPatch>>
TouchCancelInterruptArgs
Touch-cancel interrupt payload.
Properties
touches
Type: Vec<TouchMessage>
TouchEndInterruptArgs
Touch-end interrupt payload.
Properties
touches
Type: Vec<TouchMessage>
TouchMessage
One touch point in a multi-touch interrupt.
Properties
x
Type: f64
y
Type: f64
identifier
Type: i64
delta_x
Type: f64
delta_y
Type: f64
TouchMoveInterruptArgs
Touch-move interrupt payload.
Properties
touches
Type: Vec<TouchMessage>
TouchStartInterruptArgs
Touch-start interrupt payload.
Properties
touches
Type: Vec<TouchMessage>
ViewportResizeArgs
Layout viewport resize payload for the root app surface.
Properties
width
Type: f64
height
Type: f64
VisualViewportUpdateArgs
Browser visual viewport payload for page-scroll-backed root scrollers.
Properties
width
Type: f64
height
Type: f64
offset_x
Type: f64
offset_y
Type: f64
page_scroll_x
Type: f64
page_scroll_y
Type: f64
WebFontMessage
Web font payload, including family and source URL.
Properties
family
Type: Option<String>
url
Type: Option<String>
style
Type: Option<FontStyleMessage>
weight
Type: Option<FontWeightMessage>
WheelInterruptArgs
Wheel interrupt payload.
Properties
x
Type: f64
y
Type: f64
delta_x
Type: f64
delta_y
Type: f64
modifiers
Type: Vec<ModifierKeyMessage>
YoutubeVideoPatch
Create/update patch for an embedded YouTube video.
Properties
id
Type: u32
parent_frame
Type: Option<Option<u32>>
z_index
Type: Option<i32>
transform
Type: Option<Vec<f64>>
size_x
Type: Option<f64>
size_y
Type: Option<f64>
opacity
Type: Option<f64>
url
Type: Option<String>
Enums
ColorMessage
Serializable color payload for native/chassis messages.
Variants
Rgba([f64; 4])
Rgb([f64; 3])
FontPatch
Serializable font selection payload.
Variants
System(SystemFontMessage)
Web(WebFontMessage)
Local(LocalFontMessage)
FontStyleMessage
Serializable font-style value.
Variants
Normal
Italic
Oblique
FontWeightMessage
Serializable font-weight value.
Variants
Thin
ExtraLight
Light
Normal
Medium
SemiBold
Bold
ExtraBold
Black
ImageLoadInterruptArgs
Image-load response, either by pointer or copied metadata.
Variants
Reference(ImagePointerArgs)
Data(ImageDataArgs)
ModifierKeyMessage
Normalized keyboard modifier identifier.
Variants
Shift
Control
Alt
Command
MouseButtonMessage
Normalized mouse button identifier.
Variants
Left
Right
Middle
Unknown
NativeInterrupt
Events and data packets sent from the chassis back into the Pax runtime.
Variants
ChassisResizeRequestCollection(Vec<ChassisResizeRequestArgs>)
TextMeasurementResponse(TextMeasurementResponseArgs)
SelectStart(SelectStartArgs)
Focus(FocusInterruptArgs)
Scroll(ScrollInterruptArgs)
TouchStart(TouchStartInterruptArgs)
TouchMove(TouchMoveInterruptArgs)
TouchEnd(TouchEndInterruptArgs)
TouchCancel(TouchCancelInterruptArgs)
KeyDown(KeyDownInterruptArgs)
KeyUp(KeyUpInterruptArgs)
KeyPress(KeyPressInterruptArgs)
Click(ClickInterruptArgs)
Tap(TapInterruptArgs)
DoubleClick(DoubleClickInterruptArgs)
MouseMove(MouseMoveInterruptArgs)
Wheel(WheelInterruptArgs)
MouseDown(MouseDownInterruptArgs)
MouseUp(MouseUpInterruptArgs)
ContextMenu(ContextMenuInterruptArgs)
Image(ImageLoadInterruptArgs)
AddedLayer(AddedLayerArgs)
TextInput(TextInputArgs)
FormCheckboxToggle(FormCheckboxToggleArgs)
FormDropdownChange(FormDropdownChangeArgs)
FormSliderChange(FormSliderChangeArgs)
FormRadioListChange(FormRadioListChangeArgs)
FormTextboxChange(FormTextboxChangeArgs)
FormTextboxInput(FormTextboxInputArgs)
FormButtonClick(FormButtonClickArgs)
PhotoPicker(PhotoPickerInterruptArgs)
ScrollerPosition(ScrollerPositionInterruptArgs)
BrowserConfig(BrowserConfigInterruptArgs)
RenderSurfaceUpdate(RenderSurfaceUpdateArgs)
ViewportResize(ViewportResizeArgs)
RouteChange(RouteChangeInterruptArgs)
VisualViewportUpdate(VisualViewportUpdateArgs)
Gyro(GyroInterruptArgs)
Accel(AccelInterruptArgs)
DropFile(DropFileArgs)
Screenshot(ImageLoadInterruptArgs)
NativeMessage
Messages emitted by the runtime to create, update, delete, or configure native/chassis resources.
Variants
TextCreate(AnyCreatePatch)
TextUpdate(TextPatch)
TextDelete(u32)
FrameCreate(AnyCreatePatch)
FrameUpdate(FramePatch)
FrameDelete(u32)
EventBlockerCreate(AnyCreatePatch)
EventBlockerUpdate(EventBlockerPatch)
EventBlockerDelete(u32)
CheckboxCreate(AnyCreatePatch)
CheckboxUpdate(CheckboxPatch)
CheckboxDelete(u32)
NativeImageCreate(AnyCreatePatch)
NativeImageUpdate(NativeImagePatch)
NativeImageDelete(u32)
YoutubeVideoCreate(AnyCreatePatch)
YoutubeVideoUpdate(YoutubeVideoPatch)
YoutubeVideoDelete(u32)
TextboxCreate(AnyCreatePatch)
TextboxUpdate(TextboxPatch)
TextboxDelete(u32)
SliderCreate(AnyCreatePatch)
SliderUpdate(SliderPatch)
SliderDelete(u32)
DropdownCreate(AnyCreatePatch)
DropdownUpdate(DropdownPatch)
DropdownDelete(u32)
RadioListCreate(AnyCreatePatch)
RadioListUpdate(RadioListPatch)
RadioListDelete(u32)
ButtonCreate(AnyCreatePatch)
ButtonUpdate(ButtonPatch)
ButtonDelete(u32)
PhotoPickerCreate(AnyCreatePatch)
PhotoPickerUpdate(PhotoPickerPatch)
PhotoPickerDelete(u32)
GlassSurfaceCreate(AnyCreatePatch)
GlassSurfaceUpdate(GlassSurfacePatch)
GlassSurfaceDelete(u32)
ScrollerCreate(AnyCreatePatch)
ScrollerUpdate(ScrollerPatch)
ScrollerDelete(u32)
ImageLoad(ImagePatch)
ShrinkLayersTo(u32)
NativeMaskUpdate(NativeMaskPatch)
Navigate(NavigationPatch)
SetCursor(SetCursorPatch)
Screenshot(ScreenshotPatch)
TextAlignHorizontalMessage
Serializable horizontal text alignment.
Variants
Left
Center
Right
TextAlignVerticalMessage
Serializable vertical text alignment.
Variants
Top
Center
Bottom
http_api
Shared request and response types for Pax's public HTTP service.
This module deliberately contains only the closed wire contract. HTTP clients, provider mappings, persistence, and policy belong at the respective edges.
Structs
LatestReleaseResponse
Response returned by [CLI_LATEST_RELEASE_PATH].
Properties
latest_version
Type: String
TelemetryRequest
One privacy-bounded CLI telemetry event.
Properties
installation_id
Type: String
Random UUID v4 representing one OS-user CLI installation.
cli_version
Type: String
host_os
Type: HostOs
host_arch
Type: HostArch
event
Type: TelemetryEvent
Enums
CommandFamily
Public top-level CLI command families eligible for telemetry.
Variants
Create
Run
Build
Clean
Eject
Format
Lsp
Docs
Dev
SvgImport
CommandOutcome
Coarse command result. Error details never cross the HTTP boundary.
Variants
Succeeded
Failed
HostArch
Coarse host CPU architecture.
Variants
X86_64
Aarch64
Other
HostOs
Coarse host operating-system family.
Variants
Macos
Linux
Windows
Other
Target
Supported Pax build or run targets.
Variants
Web
Macos
Ios
Ipados
TelemetryEvent
Closed set of telemetry events accepted by the launch API.
Variants
CommandOutcome { command: CommandFamily, target: Option<Target>, outcome: CommandOutcome }
RunReady
Constants
CLI_LATEST_RELEASE_PATH
Path for querying the latest published pax-cli release.
CLI_TELEMETRY_PATH
Path for submitting a single CLI telemetry event.
pax-gpu
Submodules
Type Aliases
Box2D
Axis-aligned box type used for retained-node bounds.
Point2D
Point type used by tessellation and retained scene geometry.
Transform2D
Affine transform type used by the retained GPU renderer.
Vector2D
Vector type used by tessellation and retained scene geometry.
render_backend
Submodules
Structs
CapturedFrame
CPU-readable screenshot payload captured from a rendered frame.
Properties
width
Type: u32
height
Type: u32
rgba
Type: Vec<u8>
GpuContext
Shared GPU device context used by sibling render surfaces.
Image
Decoded RGBA image data ready for upload as a GPU texture.
Properties
rgba
Type: Vec<u8>
pixel_width
Type: u32
pixel_height
Type: u32
RenderBackend
Low-level wgpu backend that owns surface, pipeline, and GPU buffers.
Implementations
get_clip_depth
pub fn get_clip_depth(&mut self) -> u32
Current stencil clip depth.
max_surface_dimension
pub fn max_surface_dimension(&self) -> u32
Maximum texture dimension supported by the active adapter.
resize
pub fn resize(&mut self, width: u32, height: u32)
Resize both the physical surface and logical viewport.
resize_surface
pub fn resize_surface(&mut self, width: u32, height: u32)
Resize the backing surface, clamping to device limits.
set_viewport
pub fn set_viewport(&mut self, width: f32, height: f32, dpr: [f32; 2])
Update logical viewport uniforms without reallocating the surface.
RenderConfig
GPU resource sizing and initial surface configuration.
Properties
debug
Type: bool
initial_width
Type: u32
initial_height
Type: u32
initial_dpr
Type: [f32; 2]
Implementations
new
pub fn new(_debug: bool, width: u32, height: u32, dpr: [f32; 2]) -> Self
Construct default buffer capacities for an initial surface size.
with_browser_premultiplied_alpha
pub fn with_browser_premultiplied_alpha(self, enabled: bool) -> Self
Request premultiplied browser canvas alpha for WebGPU surfaces.
wgpu's web backend currently reports only Opaque alpha in surface capabilities, but its
configure path accepts PreMultiplied and maps it to GPUCanvasAlphaMode::Premultiplied.
Pax needs that for transparent browser-owned scroller islands.
render_backend::stencil
Structs
ClipDraw
One clip geometry instance to draw into the stencil buffer.
Properties
clip_id
Type: u32
geometry_signature
Type: u64
geometry
Type: &'a VertexBuffers<Vertex, u16>
StencilRenderer
Maintains the stencil stack used to render nested vector clips.
Implementations
new
pub fn new(device: &Device, width: u32, height: u32, sample_count: u32, color_format: TextureFormat, globals: &Buffer, clip_transforms: &Buffer) -> Self
Create the stencil pipelines and backing texture.
Vertex
Tessellated stencil vertex.
Properties
position
Type: [f32; 2]
render_context
Structs
AlphaMaskPaint
Vector paint used to construct a surface-local alpha mask.
Properties
path
Type: Path
transform
Type: Transform2D
fill
Type: Fill
opacity
Type: f32
Color
Linear RGBA color used by the low-level renderer.
Implementations
hlca
pub fn hlca(h: f32, l: f32, c: f32, a: f32) -> Self
Construct a color from HLC/Lab-style components plus alpha.
hsva
pub fn hsva(h: f32, s: f32, v: f32, a: f32) -> Self
Construct a color from HSV plus alpha, with hue normalized to 0.0-1.0.
rgba
pub fn rgba(r: f32, g: f32, b: f32, a: f32) -> Self
Construct a color from linear RGBA channels in the range 0.0-1.0.
DrawRange
Normalized visible range for a stroked vector path.
Properties
start
Type: f32
end
Type: f32
enabled
Type: bool
GradientStop
One color stop in a GPU gradient fill.
Properties
color
Type: Color
stop
Type: f32
Material
Light-reactive material coefficients for a tessellated vector path.
Properties
coefficients
Type: [f32; 4]
emissive
Type: [f32; 4]
unlit
Type: bool
ResourceChurnStats
Resource churn counters for renderer profiling.
Properties
flushes
Type: u64
retained_scene_resets
Type: u64
vector_batch_flushes
Type: u64
vector_buffer_rebuilds
Type: u64
vector_geometry_rebuilds
Type: u64
vector_geometry_cache_hits
Type: u64
vector_geometry_cache_misses
Type: u64
vector_geometry_cache_evictions
Type: u64
vector_geometry_cache_bytes
Type: u64
tessellated_vertices
Type: u64
tessellated_indices
Type: u64
cached_vertices_reused
Type: u64
cached_indices_reused
Type: u64
vector_resource_creates
Type: u64
vector_resource_updates
Type: u64
vector_resource_recreates
Type: u64
vector_resource_create_bytes
Type: u64
vector_resource_update_bytes
Type: u64
vector_resource_cache_hits
Type: u64
vector_resource_cache_misses
Type: u64
vector_resource_cache_evictions
Type: u64
vector_resource_cache_bytes
Type: u64
texture_creates
Type: u64
texture_upload_bytes
Type: u64
retained_nodes_considered
Type: u64
retained_nodes_visible
Type: u64
retained_draw_batches
Type: u64
retained_draws
Type: u64
retained_vector_draws
Type: u64
retained_image_draws
Type: u64
SceneLight
A resolved scene light for the low-level renderer.
Properties
shape
Type: LightShape
position
Type: [f32; 3]
direction
Type: [f32; 3]
color
Type: Color
intensity
Type: f32
radius
Type: f32
SceneLighting
Resolved lighting state for one retained vector scene.
Properties
active
Type: bool
ambient_is_authored
Type: bool
ambient_color
Type: Color
ambient_intensity
Type: f32
lights
Type: Vec<SceneLight>
Stroke
Stroke style for a tessellated vector path.
Properties
fill
Type: Fill
weight
Type: f32
cap
Type: StrokeCap
join
Type: StrokeJoin
WgpuRenderer
Retained scene renderer that records Pax vector/image commands and flushes them through wgpu.
Implementations
clip_alpha
pub fn clip_alpha(&mut self, paints: Vec<AlphaMaskPaint>, feather: f32)
Installs a painted alpha mask in the current save/restore scope.
current_transform
pub fn current_transform(&self) -> Transform2D
Current transform at the top of the render-state stack.
fill_path
pub fn fill_path(&mut self, path: Path, fill: Fill)
Queue a filled vector path into the current retained node.
fill_path_with_material_and_opacity
pub fn fill_path_with_material_and_opacity(&mut self, path: Path, fill: Fill, material: Material, opacity: f32)
Queue a filled vector path with material response and an extra opacity multiplier.
fill_path_with_material_and_opacity_and_smoothing
pub fn fill_path_with_material_and_opacity_and_smoothing(&mut self, path: Path, fill: Fill, material: Material, opacity: f32, smoothing: PathSmoothing)
Queue a filled vector path with material response, opacity, and optional smoothing.
fill_path_with_opacity
pub fn fill_path_with_opacity(&mut self, path: Path, fill: Fill, opacity: f32)
Queue a filled vector path with an extra opacity multiplier.
new
pub fn new(render_backend: RenderBackend<'w>) -> Self
Create a retained renderer around a low-level RenderBackend.
reset_retained_scene
pub fn reset_retained_scene(&mut self)
Drop retained scene state for a surface that has been rebound to a new tile origin.
set_surface_transform
pub fn set_surface_transform(&mut self, transform: Transform2D)
Set the base transform for the physical surface tile being rendered.
share_vector_caches_from
pub fn share_vector_caches_from(&mut self, other: &Self)
Share vector resource caches with another renderer for the same logical layer.
stroke_path
pub fn stroke_path(&mut self, path: Path, stroke: Stroke)
Queue a stroked vector path into the current retained node.
stroke_path_with_draw_range_and_material_and_opacity
pub fn stroke_path_with_draw_range_and_material_and_opacity(&mut self, path: Path, stroke: Stroke, material: Material, opacity: f32, draw_range: DrawRange)
Queue a draw-ranged stroked vector path with material response and an extra opacity multiplier.
stroke_path_with_draw_range_and_material_and_opacity_and_smoothing
pub fn stroke_path_with_draw_range_and_material_and_opacity_and_smoothing(&mut self, path: Path, stroke: Stroke, material: Material, opacity: f32, draw_range: DrawRange, smoothing: PathSmoothing)
Queue a draw-ranged stroked vector path with material response, opacity, and optional smoothing.
stroke_path_with_material_and_opacity
pub fn stroke_path_with_material_and_opacity(&mut self, path: Path, stroke: Stroke, material: Material, opacity: f32)
Queue a stroked vector path with material response and an extra opacity multiplier.
stroke_path_with_material_and_opacity_and_smoothing
pub fn stroke_path_with_material_and_opacity_and_smoothing(&mut self, path: Path, stroke: Stroke, material: Material, opacity: f32, smoothing: PathSmoothing)
Queue a stroked vector path with material response, opacity, and optional smoothing.
stroke_path_with_opacity
pub fn stroke_path_with_opacity(&mut self, path: Path, stroke: Stroke, opacity: f32)
Queue a stroked vector path with an extra opacity multiplier.
take_resource_churn_stats
pub fn take_resource_churn_stats(&mut self) -> ResourceChurnStats
Return and reset accumulated resource churn counters.
Enums
Fill
Fill style for a tessellated vector path.
Variants
Solid(Color)
Gradient { gradient_type: GradientType, pos: Point2D, main_axis: Vector2D, off_axis: Vector2D, stops: Vec<GradientStop> }
GradientType
Shape of a GPU gradient fill.
Variants
Linear
Radial
LightShape
Shape of a scene light.
Variants
Point
Directional
StrokeCap
Stroke end-cap style.
Variants
Butt
Round
Square
StrokeJoin
Stroke join style.
Variants
Miter
Round
Bevel
Architecture: Runtime & Cartridge
Historical design notes. This appendix records a direction for Pax's runtime/cartridge boundary, including proposed packaging and future logic runtimes. Its envelope and sidecar descriptions are not a specification of today's release artifact. For the current builder-facing execution model, rendering behavior, and build guidance, read How Pax Runs. Rust is the current application language. State that must survive a remount needs an owner outside the remounted component;
Propertyalone does not make state durable.
Pax borrows its core mental model from the NES.
The console is always present. It knows how to power up, read input, render frames, play audio, and expose hardware capabilities. The cartridge is what you plug in to produce one specific experience.
Pax uses the same split:
- the runtime kernel is the always-present engine
- the cartridge is the program you mount into that engine
That split is useful for two reasons:
- it keeps the runtime boundary clear
- it gives Pax a path toward multiple logic runtimes, not just Rust
The Runtime Kernel
The runtime kernel is the reusable part of Pax. It is responsible for:
- the reactive property graph
- PAXEL expression evaluation
- layout and rendering
- compositing, masking, and clipping
- event delivery
- native element projection
- platform integration for web, desktop, and mobile
You can think of the kernel as the "console." It can exist before any program is mounted, and it can later mount a cartridge.
That matters for:
- normal application startup
- design tools
- hot reload
- future online sandboxes
- future embedded use cases where a host wants to attach a program later
The Cartridge
The cartridge is the packaged running experience that gets mounted into the kernel.
Today, the cartridge is no longer thought of as "generated Rust glue" first. Instead, it is better understood as an envelope containing the parts needed to run one Pax program.
At a high level, a cartridge contains:
- a program representation
- one or more logic modules
- assets
- optional debug sidecars
Program Representation
The program representation is the declarative part of the app:
- component tree
- template nodes
- property bindings
- expressions
- timelines
- event bindings
- asset references
In debug-oriented flows, Pax still keeps a rich authoring representation around because it is useful for designtime edits and inspection.
In release-oriented flows, the goal is a smaller, more portable representation that keeps only what the runtime needs to execute the program.
Logic Modules
The logic module is the executable half of the cartridge.
Today that usually means Rust application logic: event handlers, component code, state transitions, and helper functions.
Architecturally, Pax now treats this as a separate concept from the declarative program representation. That matters because it creates a cleaner path to support additional logic runtimes in the future, such as JS or TS, while remaining compatible with the same runtime kernel and the same standard library components.
Assets
Assets are the external resources the program needs:
- images
- fonts
- videos
- static files
The runtime kernel does not hardcode application assets. The cartridge carries or references them.
Debug Sidecars
Debug builds may carry extra sidecars and metadata used for:
- source mapping
- symbolic inspection
- live edits
- reload handoff
- designtime tooling
Those are intentionally not part of the minimal release payload.
Program Representation vs. Logic Module
This boundary is worth making explicit.
The declarative program representation says things like:
- "instantiate this component"
- "bind this property to this expression"
- "attach this timeline to this field"
- "invoke this handler when this event fires"
The logic module says what the handler actually does.
That separation helps Pax in two directions at once:
- smaller release artifacts, because less behavior needs to be expanded into generated code
- cleaner multi-runtime support, because the declarative program can stay portable while the logic module can vary by host language
Native Elements Are Projections
Pax treats native elements such as browser controls or platform widgets as projections of engine state, not as the source of truth.
The authoritative state lives in the property graph inside the runtime kernel. Native elements are updated by messages from the engine and report user input back through interrupts.
That design is important for:
- deterministic rerendering
- hot reload
- future reload handoff
- consistent behavior across platforms
If durable UI state needs to survive reload or remount, it should be reflected
into Property state or another explicit durable state holder, not left hidden
inside the native control itself.
Debug and Release Builds
Debug and release builds share the same semantics, but they optimize for different goals.
Debug Builds
Debug builds optimize for editability and observability.
They keep richer metadata and support tooling such as:
- designtime workflows
- source-aware diagnostics
- live program updates
- reload handoff
- full symbolic structure for inspection
Because of that, debug builds are intentionally much larger.
Release Builds
Release builds optimize for shipping.
They aim to:
- strip metadata that the runtime does not need to execute the app
- reduce generated behavioral glue
- favor compact program representation
- keep the runtime/kernel boundary clean
- minimize transfer size, especially on web targets
Release builds intentionally do not expose the same live-edit and hot-swap surface as debug builds.
Footprint Differences
The difference in footprint between debug and release can be large, especially for web builds.
The earlier, unversioned example-size snapshot has been removed because it does not describe the current build. Measure the application and release you intend to ship, including its assets. Debug and release describes the current build modes and measurement boundaries.
Why This Architecture Matters
This runtime/cartridge split gives Pax a cleaner long-term direction.
For application authors, it means:
- the same declarative program model can target multiple platforms
- release builds can get smaller without changing authoring semantics
- debug builds can remain powerful without forcing that cost into production
For Pax itself, it means the engine is moving toward:
- a mountable runtime kernel
- a smaller and more portable program representation
- clearer host-side descriptor boundaries
- future support for additional logic languages
In short: the kernel is the console, the cartridge is the program, and the boundary between them is now becoming a first-class part of Pax rather than an implementation detail hidden inside generated code.