Skip to content

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.

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.

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.

<?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.

The compiler supports:

  • {{ expression }} interpolation;
  • :prop="expression" and conditional class bindings;
  • @press="method" native events;
  • @event="method" component events;
  • bind:value and bind:checked;
  • v-if, v-else-if, and v-else;
  • v-for over arrays, Traversable, or a positive integer;
  • default and named slots; and
  • key for stable identity.

Keep business rules in PHP methods rather than embedding them in markup.

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

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.

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.

Terminal window
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.