Form
Form with CSRF and method spoofing. Add fetch for JS-powered submission with in-page data updates, validation errors, and response handling — or let Turbo handle navigation automatically.
Basic usage
<x-boson::form action="/projects" method="POST">
<x-boson::input name="name" label="Project name" />
<x-boson::error name="name" />
<x-boson::button type="submit">Create</x-boson::button>
</x-boson::form>
With Turbo
{{-- When Turbo is installed, forms use Turbo Drive by default --}}
<x-boson::form action="/login" method="POST">
<x-boson::input name="email" label="Email" type="email" />
<x-boson::input name="password" label="Password" type="password" />
<x-boson::button type="submit">Log in</x-boson::button>
</x-boson::form>
{{-- To disable Turbo on a specific form --}}
<x-boson::form :turbo="false" action="/login" method="POST">
...
</x-boson::form>
Turbo attributes
{{-- Confirmation dialog before Turbo submits --}}
<x-boson::form action="/teams/1" method="DELETE" turbo:confirm="Delete this team?">
<x-boson::button type="submit" variant="danger" turbo:submits-with="Deleting...">
Delete Team
</x-boson::button>
</x-boson::form>
{{-- Target a Turbo Frame --}}
<x-boson::form action="/search" method="GET" turbo:frame="results">
<x-boson::input name="q" placeholder="Search..." />
</x-boson::form>
{{-- Request a Turbo Stream response --}}
<x-boson::form action="/comments" method="POST" turbo:stream>
<x-boson::textarea name="body" label="Comment" />
<x-boson::button type="submit">Post</x-boson::button>
</x-boson::form>
Fetch with in-page updates
Team name
Acme Inc.
{{-- Display area — updates in-place on success --}}
<h2 data-field="name">Acme Inc.</h2>
{{-- Add fetch for JS-powered submission --}}
<x-boson::form fetch action="/teams/1" method="PUT">
<x-boson::input name="name" label="Team name" :value="$team->name" />
<x-boson::error name="name" />
<x-boson::button type="submit">Save</x-boson::button>
</x-boson::form>
{{-- Controller --}}
public function update(Request $request, Team $team)
{
$validated = $request->validate([
'name' => ['required', 'string', 'max:255'],
]);
$team->update($validated);
// Return JSON with a `data` key → triggers in-page update
return response()->json([
'data' => $team,
]);
}
Nested data (dot-notation)
Name: Jane Doe
Email: jane@example.com
Role: Editor
{{-- Display area with dot-notation fields --}}
<p>Name: <span data-field="user.name">Jane Doe</span></p>
<p>Email: <span data-field="user.email">jane@example.com</span></p>
<p>Role: <span data-field="role">Editor</span></p>
{{-- Add fetch for JS-powered submission --}}
<x-boson::form fetch action="/users/1" method="PUT">
<x-boson::input name="name" label="Name" :value="$user->name" />
<x-boson::input name="email" label="Email" type="email" :value="$user->email" />
<x-boson::input name="role" label="Role" :value="$user->role" />
<x-boson::button type="submit">Update profile</x-boson::button>
</x-boson::form>
{{-- Controller returning nested data --}}
public function update(Request $request, User $user)
{
$user->update($request->validated());
// Nested objects flatten to dot-notation:
// { user: { name: "Jane" } } → matches [data-field="user.name"]
return response()->json([
'data' => [
'user' => [
'name' => $user->name,
'email' => $user->email,
],
'role' => $user->role,
],
]);
}
Validation errors
{{-- Place an error component after each input. Errors work with fetch forms. --}}
<x-boson::form fetch action="/users" method="POST">
<x-boson::input name="name" label="Name" />
<x-boson::error name="name" />
<x-boson::input name="email" label="Email" type="email" />
<x-boson::error name="email" />
<x-boson::button type="submit">Create user</x-boson::button>
</x-boson::form>
{{-- When the controller returns 422, errors auto-populate.
Laravel's default validation already returns:
{ errors: { email: ["The email is required."] } } --}}
Multiple input types
Project: Untitled
Description: No description yet.
Public: No
{{-- Display area --}}
<p>Project: <span data-field="project_name">Untitled</span></p>
<p>Description: <span data-field="description">No description yet.</span></p>
<p>Public: <span data-field="is_public">No</span></p>
{{-- Form with mixed input types --}}
<x-boson::form fetch action="/projects" method="POST">
<x-boson::input name="project_name" label="Project name" />
<x-boson::error name="project_name" />
<x-boson::textarea name="description" label="Description" />
<x-boson::error name="description" />
<x-boson::checkbox name="is_public" label="Make this project public" />
<x-boson::button type="submit">Create project</x-boson::button>
</x-boson::form>
{{-- Controller --}}
public function store(Request $request)
{
$validated = $request->validate([
'project_name' => ['required', 'string', 'max:255'],
'description' => ['nullable', 'string'],
'is_public' => ['boolean'],
]);
$project = Project::create($validated);
return response()->json([
'data' => [
'project_name' => $project->name,
'description' => $project->description ?: 'No description yet.',
'is_public' => $project->is_public ? 'Yes' : 'No',
],
]);
}
Redirect response
{{-- When using fetch, a standard redirect still works --}}
<x-boson::form fetch action="/projects" method="POST">
...
</x-boson::form>
{{-- Controller returning a redirect --}}
public function store(Request $request)
{
$project = Project::create($request->validated());
// Standard redirect → the page navigates normally
return redirect("/projects/{$project->id}");
}
Inside a modal
{{-- On success the form resets and the parent modal closes automatically --}}
<x-boson::modal title="Create team">
<x-boson::form fetch action="/teams" method="POST">
<x-boson::input name="name" label="Team name" />
<x-boson::error name="name" />
<x-boson::button type="submit">Create</x-boson::button>
</x-boson::form>
</x-boson::modal>
Behavior modifiers
{{-- Keep form values after success (skip reset) --}}
<x-boson::form fetch action="/settings" method="PUT" data-no-reset-on-success>
<x-boson::input name="timezone" label="Timezone" />
<x-boson::button type="submit">Save</x-boson::button>
</x-boson::form>
{{-- Keep modal open after success --}}
<x-boson::modal title="Add tags">
<x-boson::form fetch action="/tags" method="POST" data-no-close-on-success>
<x-boson::input name="tag" label="Tag" />
<x-boson::button type="submit">Add</x-boson::button>
</x-boson::form>
</x-boson::modal>
Full fetch example
Acme Corp
Building the future of widgets.
Owner: Jane Smith
{{-- Display area — updated in-place on form success --}}
<h3 data-field="name">{{ $team->name }}</h3>
<p data-field="description">{{ $team->description }}</p>
<p>Owner: <span data-field="owner.name">{{ $team->owner->name }}</span></p>
{{-- Edit form with fetch --}}
<x-boson::form fetch action="/teams/{{ $team->id }}" method="PUT">
<x-boson::input name="name" label="Name" :value="$team->name" />
<x-boson::error name="name" />
<x-boson::textarea name="description" label="Description">
{{ $team->description }}
</x-boson::textarea>
<x-boson::error name="description" />
<x-boson::button type="submit">Update team</x-boson::button>
</x-boson::form>
{{-- Controller: app/Http/Controllers/TeamController.php --}}
public function update(Request $request, Team $team)
{
$validated = $request->validate([
'name' => ['required', 'string', 'max:255'],
'description' => ['nullable', 'string'],
]);
$team->update($validated);
// Return `data` key → in-page update, no redirect
// Nested relations are flattened with dot-notation
return response()->json([
'data' => [
'name' => $team->name,
'description' => $team->description,
'owner' => [
'name' => $team->owner->name,
],
],
]);
}
JavaScript events
{{-- Listen to form lifecycle events (fetch forms only) --}}
<script>
const form = document.querySelector('form');
form.addEventListener('boson:submitting', (e) => {
// Fires before fetch — call e.preventDefault() to cancel
});
form.addEventListener('boson:success', (e) => {
// Fires after a 2xx response
console.log(e.detail); // Response data
});
form.addEventListener('boson:error', (e) => {
// Fires after 4xx/5xx or network error
console.log(e.detail); // { errors, status }
});
form.addEventListener('boson:submitted', (e) => {
// Fires after any response (success or error)
});
</script>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
action |
string |
— |
Form action URL |
method |
string |
POST |
HTTP method (GET, POST, PUT, PATCH, DELETE). PUT/PATCH/DELETE are automatically spoofed. |
fetch |
bool |
false |
Submit via JavaScript fetch with JSON response handling, in-page updates, and validation errors. |
turbo |
bool |
true |
Allow Turbo Drive to handle form submission. Set :turbo="false" to use standard browser submission. |
turbo:* |
string |
— |
Turbo data attributes (e.g. turbo:confirm, turbo:frame, turbo:stream). Maps to data-turbo-*. |
Data attributes
| Prop | Type | Default | Description |
|---|---|---|---|
data-no-reset-on-success |
flag |
— |
Keep form values after a successful submission instead of resetting. |
data-no-close-on-success |
flag |
— |
Keep the parent modal open after a successful submission. |
data-append-to |
string |
— |
CSS selector for a select element to append the new option to on success. |