Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Modal form closes or clears content on validation error when using Turbo (expected: stays open with errors) #154

@samueldietrick

Description

@samueldietrick

I’m building a modal form using Turbo + Alpine.js + Laravel backend. I found that Turbo doesn’t behave as expected when the form has validation errors.

Here’s what I’m trying to achieve:

I open a modal that contains a form (e.g., New contact).

I submit the form with invalid data (e.g., missing email).

The server returns 422 and the updated form inside the same , with validation errors displayed.

Expected behavior: the modal stays open, the form shows the errors, and the user can fix and resubmit.

Actual behavior:

If I use data-turbo-frame="_top", the modal closes entirely (because it triggers a full page visit).

If I remove data-turbo-frame="_top", the modal stays open, but Turbo clears the content or replaces it improperly.

The Alpine state controlling visibility is lost, or the modal ends up empty.

👉 This works as expected when I use jQuery + AJAX (see attached image): the modal stays open and the errors appear without breaking the flow.

Image

Example code
Blade modal form:

<turbo-frame id="dynamic-modal-frame">
    <div class="bg-white p-6 rounded shadow w-96 mx-auto mt-20" x-data="{ saving: false }">
        <h1 class="text-lg font-semibold mb-4">New contact</h1>

        <form action="{{ route('newcontact') }}" method="POST" data-turbo-frame="_top"
              x-on:turbo:submit-start="saving = true"
              x-on:turbo:submit-end="saving = false">
            @csrf

            <div class="mb-4">
                <label class="block text-sm font-medium mb-1">Name</label>
                <input type="text" name="name" value="{{ old('name') }}" class="w-full border rounded p-2">
                @error('name')
                    <div class="text-red-500 text-sm">{{ $message }}</div>
                @enderror
            </div>

            <div class="mb-4">
                <label class="block text-sm font-medium mb-1">Email</label>
                <input type="text" name="email" value="{{ old('email') }}" class="w-full border rounded p-2">
                @error('email')
                    <div class="text-red-500 text-sm">{{ $message }}</div>
                @enderror
            </div>

            <div class="flex justify-end">
                <button type="submit"
                        :class="saving ? 'bg-green-300' : 'bg-green-600 hover:bg-green-700'"
                        class="text-white px-4 py-2 rounded"
                        :disabled="saving">
                    Save
                </button>
            </div>
        </form>
    </div>
</turbo-frame>

Route:

Route::post('/newcontact', function (Illuminate\Http\Request $request) {
    $validated = $request->validate([
        'name' => 'required|string|max:255',
        'email' => 'required|email|max:255',
    ]);

    return redirect()->route('newcontact')
            ->with('toast', [
                'type' => 'success',
                'message' => 'Registered with success',
        ]);
})->name('newcontact');

📌What I expect
Turbo updates the frame content with validation errors without closing or clearing the modal.

The modal stays open when validation fails (so the user can see errors and fix them).

The modal closes automatically only when the form is successfully submitted (passes validation).

Alpine state (or similar frontend JS) remains intact so the modal stays open on errors.

What happens
With data-turbo-frame="_top", the modal closes (full page visit).

Without _top, the frame gets cleared or replaced, modal appears empty or broken.

📷 Screenshot
(You would attach the image you uploaded, showing the jQuery + AJAX working modal)

💡 Possible solution?
Maybe Turbo could:

Handle 422 frame replacement more gracefully without breaking modal context.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions