Laravel Daily's

Route-Based Modals in Inertia 3.x: Full Pages, Reused as Overlays in Laravel + Vue

Bright illustration of Laravel, Vue, and Inertia powering a route-based modal and slideover

A modal often starts as a local UI concern. A Boolean controls visibility. A second state object stores the selected record. Another watcher keeps the URL in sync.

That approach works for small interactions. It becomes harder to maintain when the modal contains a full form, authorization rules, validation, deferred data, or live updates.

Route-based modals take a different approach. The modal is a real Inertia page with a real Laravel route. You can render that page normally or open it as an overlay.

With Inertia Modal, @inertiaui/modal-vue, and Inertia 3.x, you can reuse the same route and page component without duplicating controllers or views.

The core idea: one route, two presentations

Imagine a users index at /users and a create screen at /users/create.

A normal Inertia link visits /users/create as a full page. A ModalLink visits the same route inside a modal. The Laravel controller and the Vue page remain the same.

<script setup>
import { ModalLink } from '@inertiaui/modal-vue'
</script>

<template>
    <ModalLink href="/users/create">
        Create user
    </ModalLink>
</template>

The destination page wraps its content with Modal:

<script setup>
import { Modal } from '@inertiaui/modal-vue'
</script>

<template>
    <Modal>
        <h1>Create user</h1>

        <!-- Form fields and actions -->
    </Modal>
</template>

This keeps the route responsible for the page. The modal library controls how that page is presented.

You do not need a separate CreateUserModal.vue file. You do not need a second controller method. You do not need to copy form validation into a client-side workflow.

Illustration showing one Laravel route rendered as both a full page and a floating Vue modal

Why this differs from client-side modal state

Classic client-side modals usually store at least three things:

  • Whether the modal is open.
  • Which record the modal should display.
  • Which URL or browser state represents the current modal.

Those values can drift apart. A user may close the modal while the URL still points to the detail page. A browser refresh may lose the selected record. A shared link may open the wrong background page.

Route-based modals make the route the source of truth.

The URL identifies the resource. Laravel loads the data. Inertia delivers the page props. Vue renders the page inside the overlay when the visit is modal-aware.

This has several practical benefits:

  • No duplicated state: The route and its props describe the modal.
  • Real browser history: Back can close the modal.
  • Deep links: Users can share a modal URL.
  • Direct visits: A copied URL still resolves through Laravel.
  • Reusable pages: The same page can work as a full screen or overlay.
  • Simpler testing: Controllers and form actions use normal HTTP routes.

This is one reason Inertia 3.x works well for Laravel SPAs. It gives you SPA navigation without requiring a separate API-driven frontend for every interaction. Laravel remains a productive PHP web framework, while Vue handles the interactive view layer.

Installing the Laravel and Vue packages

The Inertia Modal ecosystem provides a Laravel integration and the Vue adapter. Follow the official installation documentation for the current version requirements.

A typical project starts with:

composer require inertiaui/modal
npm install @inertiaui/modal-vue

The package is designed for existing Laravel and Inertia applications. Your routes can stay conventional:

Route::get('/users', [UserController::class, 'index'])
    ->name('users.index');

Route::get('/users/create', [UserController::class, 'create'])
    ->name('users.create');

The controller remains familiar:

public function create()
{
    return Inertia::render('Users/Create', [
        'roles' => Role::query()->pluck('name', 'id'),
    ]);
}

The same page can now be opened with ModalLink from the users index.

ModalLink: the declarative option

ModalLink behaves like an Inertia Link, but it presents the destination as a modal.

<ModalLink
    href="/users/create"
    navigate
    max-width="lg"
>
    Create user
</ModalLink>

The navigate prop is important when you want the modal reflected in browser history. Without navigation, the overlay can behave like a transient interaction. With navigate, the URL changes and the browser records the visit.

The package also supports modal configuration through props. For example, you can disable accidental backdrop closes:

<ModalLink
    href="/users/create"
    :close-on-click-outside="false"
    :close-explicitly="true"
>
    Create user
</ModalLink>

The available options include close behavior, width, position, padding, and panel classes. See the configuration reference for the complete list.

Slideovers for persistent workflows

A centered modal works well for short forms and focused actions. A slideover often fits editing screens, filters, notifications, and detail views better.

Add the slideover prop to the link:

<ModalLink
    href="/projects/42/edit"
    slideover
    navigate
>
    Edit project
</ModalLink>

The destination still uses the same Modal component:

<template>
    <Modal>
        <h1>Edit project</h1>

        <!-- Project form -->
    </Modal>
</template>

The presentation changes, not the page architecture. Slideovers default to a narrower panel and can be positioned on the left or right. You can adjust those defaults through putConfig:

import { putConfig } from '@inertiaui/modal-vue'

putConfig({
    navigate: true,

    slideover: {
        position: 'right',
        maxWidth: 'md',
    },
})

Use a modal when the task needs focused attention. Use a slideover when users should retain more context from the underlying page.

visitModal: opening a route from code

Some interactions are not simple links. You may need to open a modal after a custom action, from a composable, or after selecting an item in a keyboard-driven interface.

For those cases, use visitModal:

<script setup>
import { visitModal } from '@inertiaui/modal-vue'

function openCreateUser() {
    visitModal('/users/create', {
        navigate: true,
    })
}
</script>

<template>
    <button type="button" @click="openCreateUser">
        Create user
    </button>
</template>

You can pass visit data and modal configuration together:

visitModal('/projects/42/edit', {
    navigate: true,
    data: {
        tab: 'billing',
    },
    config: {
        slideover: true,
    },
})

This keeps the imperative code small. You still visit a Laravel route. You still receive an Inertia page. You simply choose the modal presentation at the point of navigation.

baseRoute and baseUrl: making direct visits work

A route-based modal becomes especially useful when its URL can stand alone.

Suppose /projects/42/edit is opened directly from a bookmark. What should appear behind the modal? Which page should the user return to after closing it?

A base route answers that question.

In the Laravel response, define the page that should sit behind the modal:

public function edit(Project $project)
{
    return Inertia::modal('Projects/Edit', [
        'project' => $project,
    ])->baseRoute('projects.index');
}

You can also provide a base URL:

return Inertia::modal('Projects/Edit', [
    'project' => $project,
])->baseUrl('/projects');

When the modal is opened from an existing page, the current page can act as the base. The configured baseRoute or baseUrl serves as the fallback for direct visits and reloads.

That distinction lets you reuse one modal route in several contexts. A project editor might open from the project index, a dashboard, or a notification panel. The originating page can remain the backdrop during an in-app visit, while the configured base keeps direct access predictable.

Read the Base Route / URL documentation before choosing your fallback. In most cases, the base should be a normal GET route that can render the surrounding page.

Bright browser-history illustration showing a modal route returning to its Laravel base page

Browser history becomes part of the interface

With navigate enabled, opening a modal pushes a route into browser history.

The sequence is straightforward:

  1. The user visits /projects.
  2. They open /projects/42/edit.
  3. The editor appears as a slideover.
  4. They press Back.
  5. The slideover closes and /projects becomes active again.

This behavior matches user expectations. The browser Back button does not unexpectedly leave the application. It closes the layer the user just opened.

Forward navigation can restore the modal route as well. Deep links also become useful for support workflows, notifications, and links shared between team members.

The URL is no longer decorative state. It describes the current application view.

Works with Inertia 3.x page features

A route-based overlay is still an Inertia page. That means it can use the same features as any other page in your application.

A modal can load expensive data through deferred props:

return Inertia::modal('Projects/Show', [
    'project' => $project,
    'activity' => Inertia::defer(
        fn () => $project->activity()->latest()->get()
    ),
]);

The modal can show its primary content immediately, then render activity when the deferred prop arrives.

It can also use polling for changing data:

<script setup>
import { usePoll } from '@inertiajs/vue3'

usePoll(5000, {
    only: ['status', 'activity'],
})
</script>

This works well for deployment status, imports, queues, payment processing, or collaborative activity feeds. When the modal closes or the user navigates away, Inertia manages the page lifecycle and request cancellation.

You can combine these features with Laravel’s existing validation, authorization, and form handling. The overlay does not require a special backend architecture.

A useful pattern for Laravel teams

Route-based modals are most valuable when the overlay contains real application behavior.

Use them for:

  • Create and edit forms.
  • Resource detail pages.
  • Filter and search panels.
  • Confirmation screens with server-side rules.
  • Notification and activity views.
  • Multi-step workflows.
  • Admin tools and internal dashboards.

They also fit teams that use Laravel beyond traditional page rendering. If you build a REST API with PHP for mobile clients or external consumers, Inertia can still serve the web application interface. The same Laravel project can support web routes, API routes, queues, authentication, and the rest of your PHP developer tools.

The important design choice is simple: make the page and route correct first. Then choose whether a particular navigation should replace the page, open a modal, or use a slideover.

Keep the route real

A client-side modal can be convenient. A route-based modal gives the interaction a durable boundary.

The controller owns data and permissions. The route owns identity. The Inertia page owns the workflow. The modal library owns presentation and history.

That separation reduces duplicated state while keeping the application responsive. It also lets a full Inertia page become an overlay without creating a second version of the feature.

For Laravel and Vue teams, that is the practical advantage: build the page once, then let context decide how users see it.

Previous
Vision in Laravel: Building Multimodal AI Features with the AI SDK
Next
Give Your Laravel Agents Real Power: A Practical Guide to Tool Calling with the AI SDK