Components
PAM Native has one retained render tree and several authoring styles. Typed trees, .pam templates, class components, functional components, and single-file *.pam.php components all become the same Renderable → Element → PNT1/PNP1 plan.
There is no WebView, JavaScript runtime, virtual DOM, or second bridge.
Typed PHP tree
Section titled “Typed PHP tree”use Pam\Native\App;use Pam\Native\Style;use Pam\Native\UI\Button;use Pam\Native\UI\Column;use Pam\Native\UI\Screen;use Pam\Native\UI\Text;
App::run(fn () => Screen::make( Column::make( Text::make('Checkout')->style( new Style(fontSize: 28), ), Button::make('Pay')->onPress($pay), )->style( new Style( flexGrow: 1, padding: 24, gap: 16, ), ),));Use typed trees for explicit construction, IDE navigation, and low-level control.
Class component and template
Section titled “Class component and template”final class Checkout extends Component{ private string $email = ''; private bool $loading = false;
public function render(): View { return View::make('screens.checkout'); }
public function pay(): void { // Business logic remains ordinary PHP. }}<Screen> <SafeAreaView class="flex-1 bg-white"> <Column class="flex-1 p-6 gap-4"> <Text height="56" fontSize="28">Checkout</Text> <Input model="email" keyboardType="email" sync="debounced" placeholder="Email" /> <Button loading="$loading" on:press="pay">Pay</Button> </Column> </SafeAreaView></Screen>Register view roots and the theme before running:
App::views( __DIR__.'/resources/native', __DIR__.'/.pam-native/views',);App::theme(Theme::pamLab());App::run(new Checkout());Templates are parsed and validated once. Expressions use a restricted engine, read component or data paths, and never call eval.
Single-file component
Section titled “Single-file component”<?php
declare(strict_types=1);
namespace App\Components;
use Pam\Native\Attributes\State;use Pam\Native\Component;
final class ProfileCard extends Component{ #[State] public bool $following = false;
public function __construct( public string $name, public ?string $subtitle = null, ) { }
public function toggleFollowing(): void { $this->following = !$this->following; $this->emit('changed', $this->following); }}?>
<template> <Column class="card p-4"> <Text class="profile-name">{{ $name }}</Text> <Text p-if="$subtitle" class="profile-subtitle"> {{ $subtitle }} </Text> <Button @press="toggleFollowing"> {{ $following ? 'Following' : 'Follow' }} </Button> <Slot /> </Column></template>
<style scoped> :root { --muted: #5C5C55; }
@font-face { font-family: "Brand"; src: url("asset://assets/fonts/Brand-Bold.ttf"); font-weight: 700; }
.profile-name { font-family: "Brand"; font-weight: 700; font-size: 15px; }
.profile-subtitle { margin-top: 2px; color: var(--muted); font-size: 13px; }</style>Register component directories:
App::components( __DIR__.'/src', __DIR__.'/.pam-native/components',);Constructor-promoted public properties become props. Required parameters are required props; PHP defaults define optional props. A changed private or readonly constructor prop remounts the child rather than mutating it.
Template syntax
Section titled “Template syntax”The compiler supports:
{{ expression }}interpolation;:prop="expression"and conditional class bindings;@press="method"native events;@event="method"component events;bind:valueandbind:checked;p-if,p-else-if, andp-else;p-forover arrays,Traversable, or a positive integer;- default and named slots; and
keyfor stable identity.
Keep business rules in PHP methods rather than embedding them in markup.
Use the PAM-native p-* directive spellings in every new or migrated template.
A component root may itself use p-if. A false root becomes an inert invisible
placeholder with no layout footprint, while multiple rendered roots remain an
error.
Expressões seguras
Section titled “Expressões seguras”As expressões preservam operadores familiares do PHP sem executar eval.
Elas aceitam aritmética, concatenação com ., ??, ternários, caminhos em
arrays/objetos e métodos públicos do componente.
Desde o PAM Native 0.5.87, templates também podem compor um conjunto explícito
de funções puras:
<Text p-if="mb_strlen(trim($query)) < 2"> Digite pelo menos dois caracteres.</Text><Text>{{ mb_strtoupper(mb_substr($displayName, 0, 1)) }}</Text><Text>{{ count($selectedIds) }} selecionados</Text>A allowlist contém trim, ltrim, rtrim, strlen, mb_strlen, substr,
mb_substr, strtolower, strtoupper, mb_strtolower, mb_strtoupper,
count e in_array. Funções de arquivo, processo, rede e qualquer outra
função PHP não listada continuam indisponíveis no markup. Regras de negócio e
efeitos colaterais devem permanecer em métodos públicos do componente.
Scoped native CSS
Section titled “Scoped native CSS”One optional <style scoped> block may follow </template>. PAM compiles its
native-safe subset to the same typed properties used by inline attributes. The
mobile application does not carry a browser CSS engine, selector runtime, or
WebView.
The deterministic cascade is:
- native tag rule;
- matching
.classrules in stylesheet source order; and - inline PAM attributes.
Static class and dynamic :class use the same component-local rules.
Reordering class names in markup does not change specificity. :root declares
reusable --variables; nested references and
var(--name, var(--fallback, value)) resolve at component compile time.
Cycles, missing values without a fallback, and excessive expansion depth fail
with the component path.
Text color, font family/size/weight/style, letter spacing, line height,
alignment, and case inherit through native layout containers and nested
.pam.php component templates. Since PAM Native 0.5.82, inherited CSS uses
private render context instead of public constructor props or component
variants. A strict icon or control inside a colored container therefore
receives only attributes authored on its own tag, while its internal text still
inherits the cascade. Logical font families stay intact during inheritance, so
a descendant that changes only its weight can select the corresponding
packaged @font-face.
PAM Native 0.5.54+ automatically prepends the conventional src/app.css sheet
to every component. It is the application-wide home for fonts, tokens, tag
defaults, and reusable semantic classes:
:root { --ink-muted: #5C5C55;}
@font-face { font-family: "Space Grotesk"; src: url("asset://assets/fonts/SpaceGrotesk-Regular.ttf"); font-weight: 400;}
Text,Input { font-family: "Space Grotesk";}src/app.css and local component styles may use nested relative @import
statements. Every import resolves from the file that declared it and must stay
inside the nearest Composer project; network imports, cycles, traversal, and
oversized graphs fail compilation. The complete graph participates in
component cache invalidation. The order is global tag rules, global classes,
local tag rules, local classes, then inline PAM attributes.
On Android, PAM Native 0.5.85+ snaps each retained frame’s absolute start and end edges to the physical-pixel grid, then derives its native width and height from those shared edges. At fractional display densities this keeps centered text-and-icon controls on one physical center and prevents one-pixel seams or drift between adjacent flex siblings.
<template> <Column class="profile-card"> <Text class="profile-name">{{ $name }}</Text> <Text p-if="$bio !== ''" class="profile-bio" :textColor="$highlighted ? '#1B7A4E' : '#5C5C55'" > {{ $bio }} </Text> </Column></template>
<style scoped> .profile-card { padding: 12px 16px; border: 1px solid #D8D7CF; border-radius: 12px; box-shadow: 3px 3px 0 #FFD23F; }
.profile-name { font-weight: 700; font-size: 15px; line-height: 19px; }
.profile-link { color: #1B7A4E; text-decoration: underline; }
.profile-bio { margin-top: 2px; font-size: 13.5px; }</style>The subset covers native dimensions and percentages, min/max constraints,
position edges and inset, flex growth/shrink/direction/wrapping, gap and
alignment, padding and margin shorthands, borders and radii, backgrounds,
opacity, box-shadow, elevation, compositor transforms, overflow, images, and text/font
properties. Logical padding-inline/block, margin-inline/block, and
inset-inline/block map to the native edges. transform supports
translateX/Y, scale/X/Y, and rotate; object-fit, visibility,
box-sizing: border-box, aspect-ratio: 16 / 9, percentage opacity,
border: none, and background: none are also compiled.
Text decoration accepts none, underline, line-through, and both the
spaced and hyphenated underline-plus-line-through form. Plain numbers and
px, dp, or pt represent PAM logical points; rem uses a stable native
root of 16 logical points.
Absolute left, top, right, and bottom offsets also accept percentages.
When both insets on an axis remain auto, an absolutely positioned flex child
uses its CSS static position. The parent’s justify-content controls the main
axis, while align-items or the child’s align-self controls the cross axis.
This keeps centered tab indicators, badges, and layered logos centered without
hard-coded device offsets.
border-radius accepts the standard one-to-four circular-radius shorthand and
compiles it to native per-corner radii; elliptical slash syntax is unsupported.
box-shadow accepts one native shadow in familiar CSS order:
x-offset y-offset [blur-radius] [spread-radius] [color], or none.
The color may appear before or after the lengths. Android and iOS draw the
typed shadow directly, including colored hard shadows; comma-separated and
inset shadows fail compilation because they have no equivalent lightweight
native contract.
Since PAM Native 0.5.84, auto-width text declared with @font-face is
measured from the selected TTF/OTF face’s real glyph advances. Rust reads and
caches those normalized metrics from the extracted application assets before
the first native mount; there is no UI-thread measurement, second render, or
visible correction. The platform renderer aligns visible glyphs from the
relevant flex axis: align-items/align-self in columns and
justify-content in rows. Missing, installed, or unsupported families retain
the allocation-free generic estimator. Explicitly sized or growing text keeps
normal start alignment unless text-align is authored. Use the familiar CSS
values left, center, and right, or the logical aliases start and end.
border, border-top, border-right, border-bottom, and border-left
accept <width> solid <color>. Directional forms compile to native edge
widths and the shared border color; no CSS parser runs on the device.
Individual border-*-color declarations are also accepted and update that
shared native color.
Stylesheet colors follow CSS syntax. PAM supports the complete named-color
set, transparent, #RGB, #RGBA, #RRGGBB, CSS #RRGGBBAA,
comma/space rgb() and rgba(), and hsl()/hsla() with alpha:
.glass { color: rgb(255 255 255 / 92%); background-color: #FFFFFF29; border: 1px solid hsl(140deg 45% 35% / 35%);}The compiler immediately normalizes these values to the protocol’s ARGB
integer. For source compatibility, an eight-digit color written directly as a
PAM attribute remains legacy #AARRGGBB. Prefer stylesheets for CSS
#RRGGBBAA; direct attributes also accept named colors, transparent, short
hex, and CSS color functions.
Template bindings support safe numeric arithmetic with conventional
precedence: +, -, *, /, integer %, and parentheses. Expressions such
as :height="72 + $bottomSpacing" stay inside PAM’s restricted interpreter;
they never use eval. PHP . concatenation is also supported for scalar,
null, and Stringable operands, so expressions such as '@'.$username remain
natural while array coercions fail explicitly. Since PAM Native 0.5.81,
right-associative PHP ?? safely falls back from null or a missing nested
array/property path:
<Text>{{ $profile['bio'] ?? $profile['username'] ?? 'Sem bio' }}</Text>Only tag selectors, .class selectors, comma-separated selectors, and
component-local :root variables are accepted. Packaged font aliases may be
declared with @font-face; each element resolves the closest weight and italic
variant before the typed tree crosses into native code. Font-family stacks are
accepted and their first family is the native preferred family. Descendant
selectors, nested rules, media
queries, and properties without a native protocol contract fail compilation
instead of being silently ignored.
overflow: hidden clips descendants to the native border path, including
rounded View, Row, Column, Pressable, and ImageBackground
containers. PAM retains the rounded path between frames on Android and uses
the UIKit layer mask on iOS; no WebView mask or application-side wrapper is
created.
Format single-file components
Section titled “Format single-file components”The Composer package installs a deterministic formatter:
vendor/bin/pam-native-format srcvendor/bin/pam-native-format --check srcIt discovers *.pam.php recursively, preserves the PHP block for the project’s
PHP formatter, indents templates and scoped CSS, and migrates deprecated
v-* directives to p-*. Compile-time @import lines are normalized and
preserved, while empty <style scoped> blocks are removed. The --check form
is intended for CI.
Core component families
Section titled “Core component families”The component reference is split by behavior so examples, events, cache policies, limits, and platform boundaries stay discoverable.
| Family | Components | Guide |
|---|---|---|
| Layout | Screen, View, Column, Row, Grid, SafeAreaView, Spacer |
Layout & views |
| Text and controls | Text, Input, TextInput, Button, Pressable, Toggle, NativeSwitch |
Text, input & controls |
| Images | Image, ImageBackground, DrawingCanvas |
Images & cache |
| Media | MediaPlayer for video and audio |
Video & audio |
| Scrolling | Scroll, ScrollView, FlatList, VirtualizedList, SectionList, VirtualGrid, RefreshControl |
Scroll & lists |
| Interaction | GestureDetector, InteractionRegion, Animated, touchable compatibility aliases |
Interaction & motion |
| Presentation | Modal, BottomSheet, ActivityIndicator, StatusBar, KeyboardAvoidingView, DrawerLayoutAndroid, InputAccessoryView |
Overlays & system UI |
| Web content | WebView |
WebView |
| Extension | CustomView and generated native factories |
Custom native views |
Navigation hosts are documented separately in Navigation.
What every component inherits
Section titled “What every component inherits”Every element can receive a stable key, typed protocol properties, children
where its contract permits them, and a Style. Layout, transforms, opacity,
accessibility, test identifiers, pointer behavior, intersection observation,
resize observation and native events remain on the shared element contract.
$card = View::make( Text::make('Native card'),) ->key('card:42') ->style(new Style( padding: 16, borderRadius: 20, backgroundColor: 0xFFFFFFFF, )) ->accessibilityLabel('Project PAM');Use enums rather than numeric magic values for coded variants. Protocol IDs remain sequential integers internally, but application code should name their meaning.
Platform renderer mapping
Section titled “Platform renderer mapping”The same numeric NodeKind reaches a platform-specific native control.
| Family | Android | iOS |
|---|---|---|
| Layout | Android ViewGroup hosts |
UIView |
| Text and buttons | Android text/control views | UILabel, UIButton |
| Input | Native editable text | PamInputField over UITextField |
| Images | Native image pipeline | UIImageView with cancelable URLSessionDownloadTask lifecycle |
| Scroll and lists | Scroll host and recycled RecyclerView lists |
UIScrollView; list virtualization still requires parity evidence |
| Toggle and progress | Native switch and indicator | UISwitch, UIActivityIndicatorView |
| Presentation | Android modal/drawer/refresh hosts | PamModalHost, PamDrawerLayout, PamRefreshContainer |
| Custom native view | Generated registry | NativeViewRegistry and generated Swift factories |
Declaring a node kind means the iOS protocol can create and update it. It does not imply that Android-only behavior, list recycling, accessibility, or every composed component state has already passed iOS verification.
Responsive grid
Section titled “Responsive grid”Grid defaults to twelve columns and accepts any PAM element. Spans can change at breakpoints, rows size to their tallest child, and the grid reflows for orientation, split screen, and foldable posture.
<Grid gutterX="16" gutterY="16"> <Column span="12" spanSm="6" spanMd="4"> <Image :source="$photo->url" aspectRatio="1" /> <Text>{{ $photo->title }}</Text> </Column></Grid>For spans, offsets, order, rich two-column image grids, VirtualGrid, flex
rules, and integer repetition, use the complete
responsive layout guide.
Generators
Section titled “Generators”pam mobile make:screen Orderspam mobile make:component MetricCardpam mobile make:native-view CameraPreviewGenerators are non-destructive. The package also ships a JSON schema for pam-native.json and custom HTML data for .pam tag completion.