Skip to content

Responsive layout

PAM Native has a real responsive layout system. Use Grid when placement depends on columns and breakpoints, Row or Column for one-dimensional flex layout, and FlatList for large recycled collections.

Grid defaults to 12 columns and accepts any PAM element as a child. Every item can define span, offset, order, and breakpoint variants.

<Grid columns="12" gutterX="16" gutterY="16">
<Column span="12" spanSm="6" spanMd="4">
<Image :source="$photo->url" aspectRatio="1" />
<Text>{{ $photo->title }}</Text>
<Button @press="openPhoto">Open</Button>
</Column>
</Grid>

Rows size to their tallest child. The grid recalculates for orientation, split screen, and foldable posture, so a cell must not assume the initial screen width.

Do not alternate items into two manually maintained arrays. A six-column span expresses two cells in every twelve-column row:

<Grid gutterX="12" gutterY="12">
<Column p-for="$photo in $photos" :key="$photo->id" span="6">
<Image
:source="$photo->url"
aspectRatio="1"
resizeMode="cover"
/>
</Column>
</Grid>

The cell is rich content, not a TextView placeholder. Images, buttons, application components, and nested layout are supported.

<Grid gutterX="16" gutterY="20">
<Column
span="12"
spanSm="6"
spanMd="4"
spanLg="3"
offsetMd="0"
order="2"
orderMd="1"
>
<ProductCard :product="$product" />
</Column>
</Grid>

Use offsets for deliberate empty columns, not for ordinary card padding. Use gutterX and gutterY for spacing between cells; use padding inside a cell for its content.

Use Row and Column when only one axis determines placement:

<Row class="items-center gap-3">
<Image :source="$avatar" width="48" height="48" />
<Column class="flex-1 gap-1">
<Text>{{ $name }}</Text>
<Text>{{ $subtitle }}</Text>
</Column>
<Button @press="follow">Follow</Button>
</Row>

Give growing text content flex-1 so trailing actions retain their measured width. Avoid fixed screen-width calculations.

p-for accepts a positive integer directly:

<Column p-for="$item in $count" :key="$item">
<Text>Repeated item {{ $item }}</Text>
</Column>

$item is 1, 2, …, $count. Zero or a negative integer produces no children. Arrays and Traversable values continue to work normally.

Use VirtualGrid for hundreds or thousands of rich items. Android materializes only the visible and prefetched cell trees through RecyclerView:

<VirtualGrid columns="2" rowHeight="224" prefetch="6">
<Pressable
p-for="$photo in $photos"
:key="$photo->id"
@press="openPhoto"
>
<Column class="p-2 gap-2">
<Image :source="$photo->url" aspectRatio="1" />
<Text>{{ $photo->title }}</Text>
</Column>
</Pressable>
</VirtualGrid>

Choose stable keys, keep cell layout deterministic, and avoid nesting a large vertical list inside another vertical scroll container.

FlatList numColumns="2" remains the specialized fast path for scalar string rows. It deliberately does not run a PHP render callback while scrolling.

SafeAreaView is the default for ordinary screen content. When a custom tab bar, player, or floating control must calculate its own geometry, read the platform insets through DeviceInfo:

use Pam\Native\System\DeviceInfo;
DeviceInfo::get(function (DeviceInfo $device): void {
$this->bottomSpacing = max($device->safeAreaBottom + 12.0, 20.0);
});

safeAreaTop, safeAreaRight, safeAreaBottom, and safeAreaLeft are reported in logical points on Android and iOS. PAM reads the active window on the native UI thread, including system bars and display cutouts, so application code does not need model-specific status-bar or navigation-bar guesses.

Keep a sensible initial value in component state because the native result is asynchronous. Let the custom chrome own the corresponding edge instead of also applying that edge through an ancestor SafeAreaView; otherwise the inset would be counted twice.

  • Wrap edge-to-edge screens in SafeAreaView.
  • Use DeviceInfo safe-area metrics when custom chrome owns an inset edge.
  • Use grid gutters or flex gaps instead of per-item negative margins.
  • Give images an intrinsic size or aspectRatio.
  • Allow text to wrap unless truncation is intentional.
  • Keep touch targets at least 44×44 points.
  • Test the smallest supported width, font scaling, landscape, and keyboard-open state.