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>{{ $name }}</Text> <Text v-if="$subtitle">{{ $subtitle }}</Text> <Button @press="toggleFollowing"> {{ $following ? 'Following' : 'Follow' }} </Button> <Slot /> </Column></template>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;v-if,v-else-if, andv-else;v-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.
Core component families
Section titled “Core component families”| Family | Components |
|---|---|
| Layout | Screen, View, Column, Row, Grid, SafeAreaView, Spacer |
| Content | Text, Image, ImageBackground |
| Input | Input, TextInput, Button, Pressable, Toggle, Switch |
| Scrolling | ScrollView, FlatList, VirtualizedList, SectionList, RefreshControl |
| Presentation | Modal, ActivityIndicator, StatusBar, KeyboardAvoidingView |
| Android | DrawerLayoutAndroid, TouchableNativeFeedback, InputAccessoryView |
| Compatibility | TouchableOpacity, TouchableHighlight, TouchableWithoutFeedback |
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 Mobile UI 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 Orders .pam mobile make:component MetricCard .pam mobile make:native-view CameraPreview .Generators are non-destructive. The package also ships a JSON schema for pam-native.json and custom HTML data for .pam tag completion.