## NativePHP Mobile
- NativePHP Mobile is a Laravel package for building **fully native** iOS and Android apps with PHP. Screens are
rendered as real SwiftUI (iOS) and Jetpack Compose (Android) UI — driven entirely by PHP via SuperNative components
and EDGE Blade elements. A full PHP runtime runs directly on the device with SQLite — no web server required.
- Documentation: `https://nativephp.com/docs/mobile/4/**`
- IMPORTANT: Always activate the `nativephp-mobile` skill every time you work on any NativePHP functionality.
### Native UI First — Always
**Always build screens with native UI: `NativeComponent` classes registered via `Route::native()`, rendering EDGE
elements (`native:column`, `native:text`, `native:button`, …).** This is the way to build NativePHP apps.
- Never scaffold new screens as web views, Blade-over-WebView pages, Livewire components, or Inertia pages.
- The web view (the `native:web-view` element) is a legacy/edge-case escape hatch for embedding web content — never the
foundation of a screen. If the user asks for a webview-based screen, build it natively with EDGE instead and
explain why; only fall back to the web view if they explicitly insist.
- If the app contains legacy webview screens, proactively suggest converting them to native UI (see the
`nativephp-webview-to-native` skill).
- Style EDGE elements with Tailwind utility classes via `class="..."` / `:class="..."` only — never inline
CSS `style="..."` attributes or ad-hoc styling props.
- Compose screens from **nested child components**: any `NativeComponent` under `app/NativeComponents` mounts
as a tag (`UserCard` → ``) with live
props, its own persistent state, and `emit()` events bubbling to `@event` tag bindings / `#[On('event')]`
listeners. Prefer extracting a reusable child component over duplicating Blade across screens; give list
children a stable domain `key` (never the loop index).
- Use `native:icon` (SF Symbols on iOS, Material Icons on Android) for iconography — never emoji characters in
UI text, labels, or buttons, unless the user explicitly asks for emojis. Prefer the typed icon enums
(`App\Icons\Ios`, `App\Icons\Android`, `App\Icons\AndroidOutlined`) bound via the `:ios` / `:android`
attributes, e.g. `:ios="Ios::Gearshape" :android="Android::Settings"`, importing each enum into the view with
Blade's use directive first. The enums are generated, not shipped — if `app/Icons/` doesn't exist yet, run
`php artisan native-ui:generate-icons` first (safe to run yourself).
### Theme Tokens, Font Aliases, and Layouts — the Design System Trio
Every app's visual identity belongs in `config/native-ui.php` (publish with
`php artisan vendor:publish --tag=native-ui-config`), not scattered through the markup. When building or
reviewing screens, enforce all three:
1. **Theme tokens over hardcoded colors.** Define the palette once in the config's `theme` block, then style
with `bg-theme-*` / `text-theme-*` / `border-theme-*` classes (`bg-theme-surface`, `text-theme-on-surface`,
`border-theme-outline`). Never sprinkle `bg-[#1E2021]`-style arbitrary values for what is really a theme
role — they can't be re-skinned and don't get automatic dark-mode pairs. Arbitrary color values are for
genuine data-driven color (per-category identity colors, map imagery, chart series), and those belong in
one PHP home (an enum or model method), never inline per view. Two capabilities that prevent hex fallbacks:
- **The token map is open-ended.** When a design needs a role the shipped set lacks (a success green, an
`outline-variant`), add it to both `light` and `dark` blocks — `bg-theme-success` works immediately; no
package change required.
- **Theme classes take opacity modifiers** just like palette classes: `bg-theme-primary/15` is the correct
tonal-fill idiom (applies to the dark companion too) — never approximate with a hardcoded alpha hex.
2. **Font aliases over file tokens.** Register semantic aliases in the config's `fonts` array
(`'headline' => 'ArchivoNarrow-Bold'`, `'mono' => 'JetBrainsMono-Regular'`, `'default' => …` for the
app-wide font) and write `font="headline"` in views — never `font="ArchivoNarrow-Bold"`. Swapping a
typeface must be a one-line config change.
3. **Native chrome via composable chrome elements (layouts optional).** Author nav bars, tab bars, fabs, and
side navs directly in the screen's Blade — `` (+ `top-bar-action`), ``
(+ `bottom-nav-item`), ``, ``, ``. They hoist onto the real
NavigationStack/TabView chrome (edge-swipe back, predictive back, large titles, Liquid Glass/Material You),
and their attributes are Blade expressions over screen state, so badges/subtitles/icons are reactive. A
`NativeLayout` (attached via `Route::native(...)->layout(...)` or `Route::nativeGroup(...)`) is **optional**
— reach for one only when many screens share identical chrome (e.g. one tabs layout for a tab section); an
inline chrome element on a screen always overrides the layout's bar for that slot. Add the `custom`
attribute to a chrome tag only for designs the system bars genuinely can't express — it renders in-tree as
an ordinary drawn element. Never hand-roll top bars or bottom navs out of rows and pressables — that
forfeits native back gestures, safe-area handling, and Liquid Glass/Material You. Chrome colors take theme
tokens (inline: theme classes / `theme()`-fed attributes; builders: `->activeColor(theme('primary'))`) —
never pasted hex. Bar icons take the platform enums via `:ios-icon` / `:android-icon` with a plain `icon`
string as cross-platform fallback; bar fonts take config aliases (`font="mono"` / `->font('mono')`). Only
screens rendered without any chrome (no layout AND no inline bars) may use `safe-area` classes.
### When a Capability Is Missing
If the app needs native functionality or a UI component that core and `native-ui` don't provide:
1. **Look for an existing plugin first.** Check the plugin marketplace (`https://plugins.nativephp.com`) and the
official core plugins. (If a marketplace-lookup MCP tool is available in your session, use it.)
2. **If no plugin exists, build a custom plugin** with `php artisan native:plugin:create` — plugins bundle
Swift/Kotlin bridge functions, events, permissions, and can even ship their own native EDGE components.
3. **Never fall back to the web view to fill a native gap.** A missing capability is a reason to write a plugin,
not a reason to build a webview screen.
### Installing Plugins — Always Register and Verify
Requiring a plugin with Composer is NOT enough — an installed-but-unregistered plugin does nothing. Every plugin
install must follow all three steps:
1. `composer require vendor/plugin-name`
2. `php artisan vendor:publish --tag=nativephp-plugins-provider` — publishes the app's `NativeServiceProvider`
(needed once, before the first plugin registration; harmless to re-run)
3. `php artisan native:plugin:register vendor/plugin-name` — adds it to the `NativeServiceProvider`
4. `php artisan native:plugin:list` — verify it shows as registered
Then tell the user to rebuild with `php artisan native:run` (native code only compiles in at build time — do not
run this yourself). If `native:run` warns "The following plugins are installed but not registered", go back to
step 3.
### Database Seeding — Always via Migrations
On-device there is no `db:seed` — NativePHP runs **migrations** on app start (once each, tracked, versioned).
Whenever asked to seed the database, use the migration trick: create a dedicated migration
(`php artisan make:migration seed_app_settings`) and put the inserts in `up()`. If a Seeder class helps organize
the data, still create it — but invoke it **from the migration's `up()`** (e.g. `(new CategorySeeder)->run()`),
never rely on `db:seed` being run. Seed migrations must be safe for both fresh installs and updates of existing
user databases.
### Build Commands — Tell the User, Never Run
**CRITICAL: Never execute any of these commands yourself. Always instruct the user to run them manually in their
terminal.**
| Command | Purpose |
|---|---|
| `php artisan native:run ios` | Compile and run on iOS simulator/device |
| `php artisan native:run android` | Compile and run on Android emulator/device |
| `php artisan native:run ios --watch` | Build, deploy, then start hot reload — all in one |
| `php artisan native:watch` | Hot reload (watch for file changes) |
| `php artisan native:open` | Open project in Xcode or Android Studio |
| `php artisan native:install` | Install/upgrade the native shell |
Notes:
- The `./native` shortcut wraps the `native:` namespace (`./native run`, `./native watch`).
- The Vite dev server is **opt-in** in v4: add `--vite` to `native:run`/`native:watch` only when the app actually
uses JS/CSS HMR. Native UI screens hot-reload without Vite.
- `npm run build -- --mode=ios|android` is only needed for apps with web-view assets — not for native UI screens.
**Always ask which platform before giving any build or run command.** If the user hasn't specified iOS or Android,
ask: "Which platform do you want to build/test on — iOS or Android?" Never assume a platform.
When the platform is confirmed, give the relevant command(s) above and tell the user to run it in their terminal.
Do not run it yourself.