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
  1. A .pax template declares the interface and binds an event.
  2. PAXEL expressions derive visible values from reactive properties.
  3. An interaction invokes a Rust handler.
  4. 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 directoryWhat it contains
Cargo.tomlThe 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 presentApplication 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

SymptomWhat to check
cargo or pax-cli is not foundConfirm 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 libraryRun rustup target add wasm32-unknown-unknown, then retry.
wasm-pack is not foundRun cargo install wasm-pack --version 0.15.0, then check wasm-pack --version.
A compiler, linker, or system library is missingRevisit your workstation setup. On Windows, use the PowerShell session where you loaded the MSVC environment.
The project destination already existsChoose a new directory name. Keep any existing project files.
The browser cannot connectCheck 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 run session 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:

FormExampleMeaning
Literaltext="Field notes"Use this value directly.
Value with a unitwidth=320px or width=100%Express a dimension in pixels or relative to the parent.
Expressionwidth={100% - 48px}Derive the value with PAXEL.
State bindingtext={self.title}Read the containing component's title property reactively.
Event binding@click=self.activateInvoke the component's activate handler when the event occurs.
Read/write bindingtext=bind:titleConnect 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 class attribute. 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:

  1. The containing component's local settings.
  2. Imported providers, in provider order.
  3. 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.

KindExamples
Number24, 0.25
Booleantrue, false
String"Field notes"
Size24px, 50%
Rotation15deg, 0.5rad
Time250ms, 1s, 5f
ColorBLUE, rgb(32, 40, 48), rgba(32, 40, 48, 128)
List[120px, None, 30%]
Contextual object{font_size: 16px, fill: BLUE}
Empty optionNone

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:

ValueWhat it provides
$viewportScene width and height in logical pixels; major, minor, aspect, landscape, portrait, and square.
$targetBoolean platform and operating-system facts, such as web, native, macos, ios, iphone, and ipad.
$frames, $millisRuntime frame count and elapsed time in milliseconds.
$gyro, $accelDevice 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.

PurposeOperatorsExample
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:

MethodBehavior
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 valueResult
trueDiscard the candidate, retain the last accepted value, and stop invalidation at the cutoff.
falseAccept 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

NeedStart with
A value derived for a templateA PAXEL formula; the compiler supplies its dependency edges.
Application state changed by an actionA property written by the Rust handler.
Derived reactive state used from RustProperty::computed, with every dependency listed explicitly.
A short side effect observing stateNodeContext::subscribe, with its node-scoped lifetime.
Fewer unchanged writesset_if_neq, when equality is inexpensive.
A stable output between a busy input and expensive consumersA 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 self gives it mutable access to the Notes instance that owns this template.
  • &NodeContext describes 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:

BindingHandler argumentUseful data
@button_clickEvent<ButtonClick>Activation of a native Button
@click, @tapEvent<Click>event.mouse.x, .y, .button, .modifiers
@mouse_moveEvent<MouseMove>Pointer position through event.mouse
@touch_start, @touch_move, @touch_end, @touch_cancelEvent<TouchStart>, Event<TouchMove>, Event<TouchEnd>, Event<TouchCancel>event.touches, including identifiers and positions
@key_down, @key_upEvent<KeyDown>, Event<KeyUp>event.keyboard.key, .modifiers, .is_repeat
@textbox_input, @textbox_changeEvent<TextboxInput>, Event<TextboxChange>event.text
@checkbox_changeEvent<CheckboxChange>event.checked
@slider_changeEvent<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:

MethodWhen it runsTypical use
on_mountWhen the component mountsInstall computations or subscriptions that need established bindings or context
on_tickOn runtime ticks while the component is activeAdvance an application simulation or other time-driven logic
on_pre_renderAfter tick handlers, in the pre-render phaseSynchronize work that must follow that tick's updates
on_unmountWhen the component is removed from the mounted treeRelease 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:

KindImplementation
Template-backed componentA #[pax] type with #[file(...)] or #[inlined(...)]; its template composes other nodes.
PrimitiveA #[pax] type with #[primitive("...")]; a Rust InstanceNode implements its runtime behavior, with no attached .pax template.
Data typeA #[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 InstanceNode implementation supplies construction and runtime hooks. A shared BaseInstance carries 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_mount to the properties and geometry it reads. Standard drawing primitives use changed_listener to mark their retained drawing dirty. Reserve update for work that cannot be driven by those dependencies; it requires opting in through requires_non_reactive_update.
  • Canvas drawing: implement render through RenderContext. Follow the retained-node begin/end protocol, use the correct surface-local transform, apply inherited opacity, and invalidate drawing when its inputs change. The current EllipseInstance demonstrates begin_bounded_canvas_node and clearing the dirty flag only after end_node succeeds. 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:

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:

  1. Open a team member and compare the outer and inner scope shown by the inspectors.
  2. Follow a team settings link. The outer team remains selected while the inner route and remainder change.
  3. Visit an unknown path and inspect the fallback's unconsumed segments.
  4. 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:

  • Router reads the current location and chooses a branch.
  • Route declares a path and the content that belongs there.
  • Link or Rust's NodeContext::navigate_to requests 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:

PatternMatches
/The root of the current router's scope.
/notesThat exact path.
/teams/:team_idOne segment captured as team_id.
/teams/:team_id/*The team path and any remaining segments.
default=trueA 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

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.

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:

FieldMeaning
route.locationThe structured location seen by this router.
route.global_locationThe full application location.
route.paramsNamed captures from this branch's :param segments.
route.remainderA list of path segments left for a nested router.
route.consumed_segmentsThe number of segments consumed before that remainder.
route.is_exactWhether 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"].
  • query maps tag to ["ui", "motion"]; repeated keys retain their values.
  • fragment is 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:

RouterInput segmentsCapturesRemainder
Outerteams / design / members / adateam_id = designmembers / ada
Team panelmembers / adamember_id = adaEmpty

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/design to /teams/engineering creates 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 /notes can 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/:slug remains 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 noindex and 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

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:

PositionDefault anchorChild's left edge
x=0%Left edge0px
x=50%Center150px
x=100%Right edge300px
x=200pxLeft edge200px

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:

ContainerMeasured axes
Vertical StackerIts extending axis: height
Horizontal StackerIts extending axis: width
Group and FrameBoth axes
ScrollerScroll-content height, separately from the viewport
LinkBoth axes from supplied content
TooltipBoth 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:

SettingResponsibilityValues
align_horizontalPlaces the text block horizontally inside the nodeTextAlignHorizontal::Left, Center, Right
align_verticalPlaces the text block vertically inside the nodeTextAlignVertical::Top, Center, Bottom
align_multilineAligns lines within the text blockTextAlignHorizontal::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:

fitResult
ImageFit::Fit (default)Preserves proportions and shows the whole image; may leave unused space
ImageFit::FillPreserves proportions and covers the bounds; crops the excess
ImageFit::StretchMaps 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:

InputTop-leftTop-rightBottom-rightBottom-left
12 or [12]12121212
[12, 6]126126
[12, 6, 3]12636
[12, 6, 3, 1]12631

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.

CommandMeaning
Point(x, y) by itselfMove 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
CloseConnect 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.

ControlEditable property or contentControl event
Buttonlabel supplies its visible title@button_click, with Event<ButtonClick>
Textboxtext: String@textbox_input while editing; @textbox_change when the platform commits a change
Checkboxchecked: bool@checkbox_change, with event.checked
Slidervalue: f64, bounded by min and max@slider_change, with event.value
Dropdownoptions: Vec<String> and selected_id: u32Bind selected_id; there is no dedicated public dropdown-change event
RadioListoptions: Vec<String> and selected_id: u32Bind 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

TargetPhotoPickerSource::LibraryPhotoPickerSource::Camera
WebBrowser file picker with image filtering and optional multiple selectionRequests the file input's camera-capture hint; the browser/device decides what UI is available
macOSImage-file selection through NSOpenPanelReturns Unavailable
iOS and iPadOSSystem photo picker with optional multiple selectionSystem 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_color is 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:

DurationClock
300ms or 0.3sElapsed milliseconds from the chassis's monotonic clock
18 or 18fRuntime 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.

CurveCharacter
LinearConstant progress through the segment
HoldKeep the first value until the segment ends
InQuadBegin slowly, then accelerate
OutQuadBegin quickly, then settle
InOutQuadAccelerate and then settle
InBack, OutBack, InOutBackAnticipation, 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:

SettingChoices
exit_modeFlow 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.kindSnap 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:

ContainerUse it for
GroupA shared transform and layout space, with content free to overflow
FrameA rectangular or rounded-rectangular clipping boundary
MaskA 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:

  1. The content to show.
  2. The subtree that supplies the mask.

The alpha property selects how that second subtree is used:

ModeSource behaviorContent support
alpha=false (default)Geometric coverage gives a hard clipping boundaryRendered and native content
alpha=truePainted alpha controls how much content remains visible at each pointGPU-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:

PropertyMeaning
width, heightThe visible viewport, placed by ordinary Pax layout
scroll_width, scroll_heightThe extent of the scrollable content pane
scroll_pos_x, scroll_pos_yThe 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 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

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:

PartResponsibility
CompilerAnalyzes the application and its templates, prepares the declarative program, and generates the connections to Rust component types and handlers
RuntimeInstantiates the scene, maintains reactive relationships, handles events, computes layout, and coordinates rendering and native-element updates
ChassisConnects 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:

  1. The Group binds @click to increment.
  2. The handler updates num_clicks, which feeds the Text's expression.
  3. It starts an eased transition on current_rotation, which feeds the Group's rotate property.
  4. The rotation settles, but handle_pre_render keeps updating ticks. 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

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:

ModeSaved .pax changesSaved application-logic changes
pax — defaultReloadRebuild and restart manually
allReloadRebuild and activate automatically on supported targets
logicWait for the next logic rebuild or restartRebuild and activate automatically on supported targets
offWait for restartRebuild 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-source replaces a component's entire .pax source file.
  • replace-node replaces 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:

ExampleStarting point
living-quilt (default)Interactive geometric tapestry with responsive components, motion, lighting, and GPU alpha masks
ink-and-lightA responsive postcard with handwriting, local assets, and Rust interaction
incrementA 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

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 targetCLI valueDevelopment workstationRequired target tools
Web browserwebmacOS, Debian/Ubuntu Linux, or WindowsRust, OS build tools, wasm32-unknown-unknown, and wasm-pack
macOS applicationmacosmacOSRust, full Xcode, and the selected Apple Rust targets
iPhone applicationiosmacOSRust, full Xcode, iOS SDK/simulator, and the selected Apple Rust target
iPad applicationipadosmacOSThe 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:

BuildOutput
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/.

  1. Build and test .pax/build/release/web/ locally.
  2. Publish that directory's contents as the site's document root, including snippets/, assets, and any dot-prefixed public files.
  3. Enable HTTPS and correct content types on the host.
  4. 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.

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 destinationRust target
macOS on Apple siliconaarch64-apple-darwin
macOS on Intelx86_64-apple-darwin
iPhone or iPad deviceaarch64-apple-ios
iOS/iPadOS simulator on Apple siliconaarch64-apple-ios-sim
iOS/iPadOS simulator on Intelx86_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.

KeyApplies toMeaning
titleCommon, web, AppleBrowser title or Apple display name; defaults to the Cargo package name
iconCommon, web, AppleImage used for generated icons, including the fallback web favicon
faviconWebExplicit favicon file; takes priority over icon-derived favicon generation
site_nameWebOpen Graph site name; defaults to the resolved web title
site_urlWebAbsolute public HTTP(S) URL, without query or fragment; required for release builds with indexable concrete routes
social_imageWebDefault social-preview image: an absolute HTTP(S) URL or a path relative to site_url
social_image_altWebDescription of the social image; configure both image fields together
bundle_identifierCommon, AppleApplication identity for the Apple host
marketing_versionCommon, AppleUser-facing version; defaults to Cargo package.version
build_numberCommon, AppleApple build number, expressed as a TOML string
development_teamCommon, AppleApple development team used during signing
info_plistCommon, AppleNested 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

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 onReferenceGuide
Reactive state and computed valuesPropertiesState and Properties
Input events and handler argumentsEventsEvents and Rust
Text and fontsTextText, Fonts, and Images
Shapes, paths, and paintDrawing values and drawing elementsDrawing and Styling
Routes and navigationRouterRouting

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

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

Type: (Size, Size)

Gradient start point in the primitive's local coordinate space.

end

Type: (Size, Size)

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

Type: Property<Color>

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

Type: (Size, Size)

Outer radius endpoint in the primitive's local coordinate space.

start

Type: (Size, Size)

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

Type: Property<Color>

The stroke color, including alpha.

width

Type: Property<Size>

The stroke width.

The type is [Size] for consistency with the wider property system, but current vector renderers interpret this value in pixels.

cap

Type: Property<StrokeCap>

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.


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.

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

Type: Property<Option<Size>>

Horizontal position.

y

Type: Property<Option<Size>>

Vertical position.

padding_x

Type: Property<Option<Size>>

Symmetric inner spacing applied to this node's child layout area on the x axis.

padding_y

Type: Property<Option<Size>>

Symmetric inner spacing applied to this node's child layout area on the y axis.

width

Type: Property<Option<Size>>

Horizontal extent.

height

Type: Property<Option<Size>>

Vertical extent.

anchor_x

Type: Property<Option<Size>>

Horizontal transform origin, relative to the node's own bounds.

anchor_y

Type: Property<Option<Size>>

Vertical transform origin, relative to the node's own bounds.

scale_x

Type: Property<Option<Size>>

Horizontal scale coefficient.

scale_y

Type: Property<Option<Size>>

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

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

Type: Property<Color>

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

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

Type: Property<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

Type: Property<Size>

Desired spacing between grouped glass surfaces, in Pax units.

interactive

Type: Property<bool>

Whether supported Apple glass surfaces should use the interactive effect.

tint

Type: Property<Option<Color>>

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

Type: Property<Duration>

Duration for the generated card enter/exit transition.

scrim_opacity

Type: Property<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

Type: Property<Duration>

Duration for the generated modal scrim enter/exit transition.

scrim_opacity

Type: Property<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 router
  • route.global_location: the full browser/native location
  • route.params: named captures from :param segments
  • route.remainder: the unmatched tail after this branch
  • route.is_exact and route.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

Type: Property<Size>

Width of the scrollable content pane.

scroll_height

Type: Property<Size>

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

Type: Property<Vec<Size>>

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

Type: Property<Vec<Size>>

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

Type: Property<TextStyle>

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

Type: Property<Font>

Font family/source/style/weight configuration.

font_size

Type: Property<Size>

Font size, in pixels.

fill

Type: Property<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

Structs

Ellipse

A 2D vector ellipse, which inscribes its bounding box with the specified fill and stroke.

Properties

stroke

Type: Property<Stroke>

Stroke drawn around the ellipse.

fill

Type: Property<Fill>

Fill painted inside the ellipse.

material

Type: Property<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

Type: Property<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

Type: Property<UnitValue>

Start position of the visible handwriting range.

draw_end

Type: Property<UnitValue>

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

Type: Property<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

Type: Property<Color>

Light color.

intensity

Type: Property<f64>

Light intensity multiplier.

radius

Type: Property<Size>

Radius for point-light attenuation.

z

Type: Property<Depth>

Logical scene depth in pixels.

shape

Type: Property<LightShape>

Positional or directional light shape.

direction

Type: Property<Vector3>

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

Type: Property<Size>

The x-coordinate of the start point.

y1

Type: Property<Size>

The y-coordinate of the start point.

x2

Type: Property<Size>

The x-coordinate of the end point.

y2

Type: Property<Size>

The y-coordinate of the end point.

stroke

Type: Property<Stroke>

The stroke used to render the segment.

material

Type: Property<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

Type: Property<Stroke>

The stroke applied along the path centerline.

fill

Type: Property<Fill>

The fill applied to the interior of closed contours.

material

Type: Property<Material>

Light-reactive surface response.

smoothing

Type: Property<PathSmoothing>

Optional curve smoothing applied before rendering path geometry.

draw_start

Type: Property<UnitValue>

Start position of the visible stroke range over the path's total length.

draw_end

Type: Property<UnitValue>

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

Type: Property<Size>

Control point x-coordinate.

y

Type: Property<Size>

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

Type: Property<Size>

Point x-coordinate.

y

Type: Property<Size>

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

Type: Property<Numeric>

Top-left corner radius.

top_right

Type: Property<Numeric>

Top-right corner radius.

bottom_right

Type: Property<Numeric>

Bottom-right corner radius.

bottom_left

Type: Property<Numeric>

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

Type: Property<Stroke>

Stroke drawn around the rectangle.

fill

Type: Property<Fill>

Fill painted inside the rectangle.

material

Type: Property<Material>

Light-reactive surface response.

corner_radius

Type: Property<CornerRadii>

Per-corner radii.

forms

Submodules

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

Type: Property<Color>

Button background color.

hover_color

Type: Property<Color>

Button background color while hovered, when supported.

corner_radius

Type: Property<f64>

Button corner radius, in pixels.

outline

Type: Property<Stroke>

Button outline stroke.

style

Type: Property<TextStyle>

Text style applied to the label.

forms::checkbox

Structs

Checkbox

A checkbox control, delegating to a platform-specific native checkbox.

Properties

background

Type: Property<Color>

The background color when unchecked

background_checked

Type: Property<Color>

The background color when checked

outline

Type: Property<Stroke>

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

Type: Property<NewItem>

Behavior when the typed text does not match any option.

background

Type: Property<Color>

Textbox/list background color.

stroke

Type: Property<Stroke>

Textbox/list stroke.

style

Type: Property<TextStyle>

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

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

Type: Property<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

Type: Property<TextStyle>

Text style for option labels.

background

Type: Property<Color>

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

Type: Property<Color>

Radio button background color when unchecked.

background_checked

Type: Property<Color>

Radio button background color when checked.

outline

Type: Property<Stroke>

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

Type: Property<TextStyle>

Text style for option labels.

forms::slider

Structs

Slider

A slider control, delegating to a platform-specific native range input.

Properties

background

Type: Property<Color>

Track background color.

accent

Type: Property<Color>

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

Type: Property<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

Type: Property<Color>

Textbox background color.

placeholder

Type: Property<String>

Placeholder text shown when empty, when supported by the chassis.

stroke

Type: Property<Stroke>

Border stroke.

corner_radius

Type: Property<f64>

Corner radius, in pixels.

style

Type: Property<TextStyle>

Text style.

outline

Type: Property<Stroke>

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

Type: Property<Size>

The height of the toast.

y_pos

Type: Property<Size>

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

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

Type: Property<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

Type: Property<Vec<Size>>

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

Type: Property<Size>

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

Type: Property<ImageFit>

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

Type: Property<ImageFit>

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_children is the raw transport family used by Slot
  • received_children is the normalized semantic payload that this node should treat as content from its caller
  • retained_received_children are former received children kept alive only so @out transitions 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

Type: Property<Gyro>

Current device orientation sensor reading.

accel

Type: Property<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

Type: Property<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.

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:

  1. 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
  2. Lifecycle slice of the received payload
    • active received children: present in NodeContext::received_children
    • retained exiting received children: present in NodeContext::retained_received_children

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

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

Type: Property<Gyro>

accel

Type: Property<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:

  1. Expand nodes & compute properties; recurse entire instance tree and evaluate ExpandedNodes, stitching together parent/child relationships between ExpandedNodes along the way.
  2. Compute layout (z-index & TransformAndBounds) by visiting ExpandedNode tree in rendering order, writing computed rendering-specific values to ExpandedNodes
  3. 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

Type: Property<PaxValue>

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

Type: Property<PaxValue>

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.

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

Type: Property<Numeric>

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

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


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


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>

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)
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)
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; Property alone 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:

  1. it keeps the runtime boundary clear
  2. 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.