laravel-livewire-multistep-form maintained by codegenie-be
Laravel Livewire Multi-Step Form
A small, configuration-driven multi-step form component for Laravel Livewire. It provides step navigation, per-step validation, a review step, accessible markup, localized interface copy, and a validated submission hook without taking ownership of your application's persistence layer.
Requirements
The package declares support for:
- PHP 8.2 through supported PHP 8.x releases;
- Laravel 12 or 13;
- Livewire 3.6+ or 4.x.
GitHub Actions verifies both recent and minimum resolvable dependency sets. The matrix covers Laravel 12/13, Livewire 3/4, PHP 8.2/8.3/8.4/8.5 where applicable, plus dedicated --prefer-lowest --prefer-stable jobs for every supported Laravel/Livewire major combination.
Installation
Install the stable package through Composer:
composer require codegenie-be/laravel-livewire-multistep-form
If Packagist or a Composer mirror has not indexed the newest release yet, the repository can still be installed directly as a VCS package:
composer config repositories.codegenie-livewire-multistep-form vcs https://github.com/Codegenie-BE/laravel-livewire-multistep-form
composer require codegenie-be/laravel-livewire-multistep-form:dev-main
Laravel package discovery registers the service provider automatically.
Basic usage
Define the fields in your application and pass them to the registered Livewire component:
<livewire:codegenie-multistep-form
:fields="[
'name' => [
'default' => '',
'rules' => 'required|string|min:2|max:120',
'label' => 'Name',
'step' => 1,
'type' => 'text',
'placeholder' => 'Your name',
],
'email' => [
'default' => '',
'rules' => 'required|email|max:255',
'label' => 'Email address',
'step' => 2,
'type' => 'email',
],
'topic' => [
'default' => '',
'rules' => 'required|string',
'label' => 'Topic',
'step' => 2,
'type' => 'select',
'placeholder' => 'Choose a topic',
'options' => [
'general' => 'General question',
'support' => 'Support',
],
],
'message' => [
'default' => '',
'rules' => 'required|string|min:10|max:5000',
'label' => 'Message',
'step' => 3,
'type' => 'textarea',
'placeholder' => 'How can we help?',
],
]"
/>
Steps must start at 1 and remain consecutive. Multiple fields may share the same step.
Field configuration
Each field supports these keys:
| Key | Required | Description |
|---|---|---|
type |
yes | One of text, email, number, tel, url, date, textarea, or select |
rules |
conditionally | Laravel validation rules as a non-empty string or an array of non-empty strings; may be empty when serverValidationRules() supplies the field rules |
step |
yes | Positive integer step number |
label |
no | Human-readable label; generated from the field name when omitted |
default |
no | Scalar or null initial value; defaults to an empty string. Select defaults are more restrictive as described below. |
placeholder |
no | Non-empty placeholder text for text-like fields, textarea, or select |
options |
for select |
Non-empty value-to-label map; option values form the server-side allow-list |
Every field must have at least one configured validation rule or one server-only validation rule. Unsupported field types are rejected instead of being rendered with undefined behavior. File uploads, checkboxes, radio groups, repeaters, and nested field names are intentionally outside the current scope.
Select fields
Select option keys are normalized to strings because browser and Livewire form values are string-based. The package adds the configured option keys to the same Laravel validator used for the rest of the field rules, so consumers do not need to duplicate the option list in an in: rule.
Select defaults may only be strings, integers, or null; integer defaults are normalized to strings. Booleans and floating-point defaults are rejected to avoid ambiguous browser value coercion. A non-empty select default must exist in options. The empty value is reserved for the unselected / placeholder state. On the review step, the human-readable option label is displayed instead of the raw option key.
Validation
nextStep() validates only the fields on the current step. Native form submission on an input step, including pressing Enter in a compatible control, is routed through nextStep() rather than final submission.
submit() is accepted only on the dynamic review step and validates the complete form again. A direct attempt to invoke final submission before the review step is ignored server-side. If final revalidation fails, the wizard returns to the step containing the first invalid configured field and requests focus for that field. Select values are constrained to their configured option keys as part of the same validation pass.
For Laravel rules containing pipe characters as part of the rule itself, such as a regular expression, use array rule syntax:
'rules' => [
'required',
'regex:/^(foo|bar)$/',
],
Server-only Laravel rules
The declarative rules value is part of the locked public Livewire field configuration. It is appropriate for ordinary serializable rules such as required|string|max:255, but rule configuration that contains application internals or Laravel rule objects should remain on the server.
Extend the component and override serverValidationRules() for those rules. Keys are the configured field names without the formData. prefix:
<?php
namespace App\Livewire;
use Codegenie\LivewireMultistepForm\Livewire\MultiStepForm;
use Illuminate\Validation\Rule;
class ContactWizard extends MultiStepForm
{
protected function serverValidationRules(): array
{
return [
'email' => [
Rule::unique('users', 'email'),
],
];
}
}
The returned rules are rebuilt on each Livewire request and merged with the declarative field rules. Laravel rule objects and closures can therefore be used without placing them in public Livewire state. A server-only rule may only target a configured field; unknown field names are rejected instead of silently validating unrelated data.
Configured defaults are initialized before serverValidationRules() is first evaluated during mount, so server-only rules may safely inspect default-driven formData values. A field may also leave rules empty and define its complete validation contract in serverValidationRules(). If native browser required semantics are desired, keep a simple required rule in the declarative field configuration as well; the server remains authoritative either way.
Validation errors use the same formData.* keys as the Livewire bindings and are rendered with accessible error relationships.
Handling submissions
The base package deliberately does not write to a database, send mail, or redirect to an application-specific route. A reusable UI package should not decide how your application stores submitted data.
On a valid review-step submission it:
- validates the complete form, including configured select allow-lists and server-only rules;
- calls the protected
handleSubmission(array $data)extension point; - dispatches the
multistep-form-submittedLivewire event with the validated configured data; - resets the wizard to its configured defaults.
For server-side persistence, extend the component in your application:
<?php
namespace App\Livewire;
use App\Models\ContactRequest;
use Codegenie\LivewireMultistepForm\Livewire\MultiStepForm;
class ContactWizard extends MultiStepForm
{
protected function handleSubmission(array $data): void
{
ContactRequest::query()->create($data);
}
}
Register and render that application component using normal Livewire conventions. Keep authorization, persistence, mail delivery, rate limiting, and other application-specific concerns in your application.
Localization
The default interface ships with English, Dutch, and French translations. It follows the consuming Laravel application's active locale.
Consumers may publish the translations and override individual strings:
php artisan vendor:publish --tag=livewire-multistep-form-translations
Published translations are placed under Laravel's normal vendor translation path.
Customizing the view
The default Blade view can be published when an application needs markup or styling changes:
php artisan vendor:publish --tag=livewire-multistep-form-views
The published view is placed under:
resources/views/vendor/livewire-multistep-form/
When customizing it, preserve the validation bindings, review-step submission guard, escaped review output, instance-scoped DOM IDs, and accessibility relationships.
Tailwind CSS
The package ships Blade markup with Tailwind utility classes but does not install or compile frontend assets for the consuming application.
Tailwind CSS 4
Add the package views as a source in your application's CSS entrypoint. Adjust the relative path when your CSS file lives elsewhere:
@source '../../vendor/codegenie-be/laravel-livewire-multistep-form/resources/views/**/*.blade.php';
Tailwind CSS 3
Add the package views to the content array in tailwind.config.js:
content: [
'./resources/**/*.blade.php',
'./vendor/codegenie-be/laravel-livewire-multistep-form/resources/views/**/*.blade.php',
],
You may pass six-digit hexadecimal colors for the progress indicator and primary action:
<livewire:codegenie-multistep-form
:fields="$fields"
primary-color="#216ef2"
button-color="#216ef2"
/>
The package validates the color format. The consuming application remains responsible for choosing colors with sufficient contrast.
Security characteristics
- Field configuration, current step, and color configuration are locked Livewire state.
- Declarative field rules are public Livewire state and should not contain sensitive application internals.
serverValidationRules()keeps complex or sensitive rule configuration server-side.- User-controlled form values remain mutable and are always validated server-side.
- Final submission is accepted only on the review step and revalidates the complete form.
- Select values are checked against their configured server-side option allow-lists.
- Only configured and validated fields are passed to the submission hook.
- Review values are escaped before rendering.
- Inline color values accept only six-digit hexadecimal values.
- The package does not store data, manage credentials, run queues, require Redis, or call external services.
The application's own authorization, persistence, rate limiting, privacy policy, and data-retention rules remain the responsibility of the consuming project.
See SECURITY.md for vulnerability reporting guidance.
Accessibility
The default view includes:
- semantic form, fieldset, label, and definition-list markup;
- a progressbar with ARIA value metadata;
- instance-scoped DOM IDs so multiple wizards can coexist on one page;
aria-invalidandaria-describedbyon invalid controls;- alert semantics for validation errors;
- explicit button types;
- visible keyboard focus styles;
- instance-scoped focus management after navigation, resets, submissions, and validation failures;
- reduced-motion-aware transitions and loading indicators;
- localized screen-reader and interface copy.
Consumers who publish their own view should preserve equivalent semantics.
Development
Install dependencies:
composer update
Run the quality gates:
composer test
composer format
composer analyse
composer audit
The repository CI additionally runs recent and minimum compatibility matrices. Larastan currently runs at level 8, and the aggregate Required checks job only passes when compatibility and quality jobs all succeed.
Contributing
See CONTRIBUTING.md. Keep changes focused and Laravel-native. New field types must include validation, rendering behavior, accessibility handling, and regression tests for their supported states.
Changelog
See CHANGELOG.md for stable release notes and unreleased changes.
License
MIT. See LICENSE.
Built and maintained by Codegenie.