Bifrost turns 1! Get 30% off annual plans with code
HAPPYBIRTHDAY

Routing


Overview#

Native screens form a stack, exactly like a native iOS UINavigationController or an Android Activity stack. From inside a NativeComponent, you push, pop, and replace screens using methods on $this.

Registering routes#

Native routes are registered similarly to Livewire routes, but instead of using Route::livewire() you use the Route::native() macro:

Copied!
use App\NativeComponents\Home;
use App\NativeComponents\ItemDetail;
 
Route::native('/', Home::class);
Route::native('/item/{id}', ItemDetail::class);

You can put these routes anywhere that makes sense for your application, but you may find that creating a routes/mobile.php to keep them all together is a clean option.

Route parameters work just like Laravel web routes — {id} matches a path segment and is exposed to the screen through $this->param('id').

See Layouts for how to attach shared chrome to a route or group of routes, and Middleware for guarding screens with ->middleware().

Pushing a new screen#

Copied!
$this->navigate('/item/42');

By default a push slides in from the right. Pass arbitrary data along with the navigation:

Copied!
$this->navigate('/item/42', ['source' => 'home-feed']);

The next screen reads the data with $this->data('source').

Going back#

Copied!
$this->back();

Pops the current screen off the stack and returns to whatever was beneath it. The default transition is a slide-out to the right.

Replacing the current screen#

Copied!
$this->replace('/login');

Pops the current screen and pushes a new one in its place — useful after sign-in / sign-out or for in-place tab swaps. The default transition is a fade.

Exiting to the web view#

Copied!
$this->exitToWeb('/dashboard');

Tear down the native UI stack and load the given URL in the web view. Use this for screens that aren't part of the native experience.

Custom transitions#

Chain transition() after a navigation method to override the default animation:

Copied!
use Native\Mobile\Edge\Transition;
 
$this->navigate('/item/42')->transition(Transition::SlideFromBottom);

Available cases:

  • Transition::SlideFromRight - default for navigate()
  • Transition::SlideFromLeft - default for back()
  • Transition::SlideFromBottom - modal-style presentation
  • Transition::Fade - default for replace()
  • Transition::FadeFromBottom - subtle vertical fade
  • Transition::ScaleFromCenter - zoom-in effect
  • Transition::ParallaxPush - incoming screen slides in from the right while the outgoing screen drifts partially left underneath
  • Transition::Morph 4.7 - cross-fade while elements that share a ref move between the two screens. See Morphing elements between screens
  • Transition::None - swap with no animation

The @navigate directive triggers navigation directly from your view, with no component method required. It works on any node, so any element can become a navigation target.

The short form takes a route in single quotes:

Copied!
<native:pressable @navigate='/item/42' class="w-full p-4">
<native:text>View item</native:text>
</native:pressable>

To pass data along with the navigation, use the args form — a route followed by an array:

Copied!
<native:pressable @navigate="'/item/42', ['source' => 'home-feed']">
<native:text>View item</native:text>
</native:pressable>

The next screen reads the data with $this->data('source'), exactly as with $this->navigate().

Double quotes hold a PHP expression rather than a plain route, so @navigate="/item/42" won't compile. For a dynamic route, build the string in PHP: @navigate="'/item/' . $item->id".

Transition modifiers#

Add a transition as a modifier to override the default push animation:

Copied!
<native:pressable @navigate.slideFromBottom='/item/42'>
<native:text>Present modally</native:text>
</native:pressable>

Available transition modifiers:

  • fade
  • slideFromRight
  • slideFromLeft
  • slideFromBottom
  • fadeFromBottom
  • scaleFromCenter
  • parallaxPush
  • morph 4.7- none

Action modifiers#

Modifiers also select the navigation action. Use these in place of a route to back, or combine them with a route to replace or exitToWeb:

Copied!
<native:pressable @navigate.back>
<native:text>Go back</native:text>
</native:pressable>
 
<native:pressable @navigate.replace='/login'>
<native:text>Sign out</native:text>
</native:pressable>
 
<native:pressable @navigate.exitToWeb='/dashboard'>
<native:text>Open dashboard</native:text>
</native:pressable>

Transition and action modifiers can be combined — @navigate.replace.fade='/login'.

Morphing elements between screens#

4.7 The morph transition carries an element from where it sits on one screen to where it sits on the next. A thumbnail grows into a header image, or an avatar slides into place on a profile page. If you've used the web's View Transitions API, it's the same idea.

Give the element the same ref on both screens, then navigate with the morph modifier:

Copied!
{{-- The album list --}}
@foreach ($albums as $album)
<native:pressable @navigate.morph="'/album/' . $album->id">
<native:image :ref="'album-' . $album->id" :src="$album->cover" class="w-16 h-16 rounded-xl" />
</native:pressable>
@endforeach
Copied!
{{-- The album screen --}}
<native:column class="w-full">
<native:image :ref="'album-' . $album->id" :src="$album->cover" class="w-full h-80" />
 
<native:pressable @navigate.back.morph>
<native:text>Back</native:text>
</native:pressable>
</native:column>

Tapping an album cross-fades the two screens while the cover moves and grows from the small tile into the header. Going back with @navigate.back.morph plays it in reverse.

From a component, pass Transition::Morph:

Copied!
use Native\Mobile\Edge\Transition;
 
$this->navigate('/album/3')->transition(Transition::Morph);

Shaping the morph#

These attributes change how an element travels. They're read from the element on the screen you're navigating to, so put them on both elements if you want the same motion in both directions.

  • morph - what moves. frame (the default) moves and resizes the element. position moves it at its final size. size resizes it in place, at its final position. none stops the element morphing.
  • morph-duration - length in milliseconds. The default is 350, the same as the cross-fade behind it.
  • morph-easing - the curve: linear, ease-in, ease-out, ease-in-out (the default) or spring.
Copied!
<native:column ref="now-playing" morph="position" morph-duration="500" morph-easing="spring">
...
</native:column>

Which elements morph#

  • An element morphs only when its ref is on both screens. Everything else cross-fades with its screen.
  • Elements only morph under the morph transition. A ref that appears on both screens does nothing during a slide or a fade.
  • Keep each ref unique within a screen. If two elements share one, you can't be sure which of them moves.
  • ref is also how tests target elements. If you've reused a test ref on two screens and don't want those elements to morph, add morph="none" to either of them.

Native chrome#

A morph needs one whole screen to replace another. Inside native chrome that doesn't always happen. Pushing from one screen with a native top bar to another, or switching between tabs of a bottom nav, is animated by the platform's own navigation, and nothing morphs.

Morphs do run between screens that have no native chrome, and whenever the chrome changes, such as a tab screen opening a detail screen that has a top bar instead. See Layouts for how screens get their chrome.

Reading params and data#

Copied!
class ItemDetail extends NativeComponent
{
public function mount(): void
{
$id = $this->param('id'); // from the route URI
$source = $this->data('source', 'unknown'); // from navigate()'s second arg
 
// ...
}
}

Both accessors take an optional default that's returned when the key is missing.

Resolving named routes#

If you've named your routes you can use route() on the component to resolve them to URIs:

Copied!
$this->navigate($this->route('listing.show', ['id' => 5]));

This delegates to Laravel's URL generator with absolute: false.

Customizing device-back behavior#

By default, the device back button (Android) pops the navigation stack. Override onBackPressed() to add custom behavior:

Copied!
class CheckoutForm extends NativeComponent
{
public bool $dirty = false;
 
public function onBackPressed(): void
{
if ($this->dirty) {
$this->showDiscardConfirmation();
 
return;
}
 
$this->back();
}
}

If the back press should ultimately still pop, call $this->back() from your override.