Multistep Forms
Turn complex multi-page forms into a single array. Flick handles breadcrumbs, navigation, validation, and a review step automatically.
$steps = [ 'Contact Info' => [ 'fields' => [ 'name' => ['label' => 'Name', 'rules' => ['required']], 'email' => ['type' => 'email', 'label' => 'Email'], ], ], 'Preferences' => [ 'fields' => [ 'newsletter' => ['type' => 'checkbox', 'label' => 'Subscribe to newsletter'], 'updates' => ['type' => 'checkbox', 'label' => 'Receive product updates'], ], ], ]; $form->createMultistep($steps);
That's it. Breadcrumbs, step validation, a review page, and session management are all included.
In the example above:
- Array keys (
'Contact Info','Preferences') become step names, shown in the breadcrumbs fieldscontains your form fields — same format as Flick'screate()methodtext(optional) adds instructions above the form for that stepbutton(optional) customizes the button text for that step
Each field's array key is its name, so newsletter posts as newsletter and shows up
under that name on the review page. Add a name key only when you want it to differ.
Flick automatically generates breadcrumb navigation, a review step at the end, and handles all session state.
Quick Start
Here's a complete working example:
<?php require 'vendor/autoload.php'; $form = new Flick\Flick(['views' => 'bootstrap']); $steps = [ 'Contact Info' => [ 'text' => 'Enter your contact information.', 'fields' => [ 'name' => ['label' => 'Name', 'rules' => ['required']], 'email' => ['type' => 'email', 'label' => 'Email', 'rules' => ['required', 'email']], ], ], 'Preferences' => [ 'text' => 'Tell us about yourself.', 'fields' => [ 'newsletter' => ['type' => 'checkbox', 'label' => 'Subscribe to newsletter', 'value' => 'yes'], ], ], ]; // Handle completion if ($form->multistepIsComplete()) { $data = $form->multistepFormData(); $form->successMessage('Thank you, ' . $data['name'] . '!'); // multistepFormData() cleared the session, so returning here stops // createMultistep() from starting a brand new wizard on this same page load return; } // Create the form $form->createMultistep($steps);
Automatic Session Handling
Flick automatically starts and manages PHP sessions for multistep forms. If your framework has its own session handler, Flick will use the existing session.
Form Structure
The array you pass to createMultistep() defines your entire wizard:
$steps = [ 'Step Name' => [ // Array key = step name (shown in breadcrumbs) 'fields' => [ // Required: same field format as create() 'field_name' => ['label' => 'Field Label'], ], 'text' => 'Instructions...', // Optional: shown above the form 'button' => [ // Optional: customize the button 'text' => 'Continue', ], ], // Add more steps... ];
The flow:
Step 1 → Step 2 → ... → Review → Complete
- Each step validates before advancing
- Users can navigate back via breadcrumbs
- A "Review" step is automatically added at the end
- Session stores all data until final submission
Field Configuration
Fields use the same syntax as Flick's create() method:
'Personal Details' => [ 'text' => 'Please provide your personal information.', 'fields' => [ 'first_name' => [ 'type' => 'text', 'label' => 'First Name', 'rules' => ['required', 'min:2', 'max:50'], 'messages' => [ 'required' => 'Please enter your first name', 'min' => 'Name must be at least 2 characters', ], ], 'birth_date' => [ 'type' => 'date', 'label' => 'Date of Birth', 'rules' => ['required', 'before:today'], ], 'country' => [ 'type' => 'select', 'label' => 'Country', 'options' => 'countries', // Use a prebuilt dropdown 'rules' => ['required'], ], ], ],
Field Names
The array key becomes the field's name, and the review page prints that name as the
row label. Use descriptive keys like first_name rather than fn.
The Review Step
After the final step, Flick automatically generates a "Review" step. Users see all their submitted data in a table and can navigate back to edit any step via breadcrumbs.
Customize the review page with options:
$options = [ 'reviewTitle' => 'Almost Done!', 'reviewText' => 'Please verify your information before submitting.', 'submitText' => 'Complete Registration', ]; $form->createMultistep($steps, $options);
Review Display
Row labels on the review page come from the field name, run through ucwords() —
underscores are not replaced. first_name shows as "First_name", not
"First Name". If that matters, build your own review UI with
manual mode, where you control the labels.
Handling Completion
When the user clicks submit on the review page, the form is complete.
// Always check BEFORE createMultistep() if ($form->multistepIsComplete()) { // Get all form data and clear the session $data = $form->multistepFormData(); // Process the data // - Save to database // - Send confirmation email // - Redirect to thank you page $form->successMessage('Your application has been submitted!'); return; // see the note below } $form->createMultistep($steps);
Warning
multistepFormData() destroys the wizard's session as it hands you the data. That
means multistepIsComplete() is already false by the time createMultistep()
runs on the same page load, and it will render a fresh step 1 rather than
nothing. Redirect, return, or exit after you've processed the data — or read
without clearing via multistepFormData(false) or multistepReviewData().
Data Retrieval Methods
| Method | Returns | Clears Session |
|---|---|---|
multistepFormData() |
All submitted data | Yes (default) |
multistepFormData(false) |
All submitted data | No |
multistepReviewData() |
All submitted data | No |
Use multistepReviewData() if you need to display data on a custom review page without ending the session.
Customization Options
Pass options as the second argument to createMultistep():
$options = [ 'auto' => true, 'reviewTitle' => 'Review Your Information', 'reviewText' => 'Take a moment to verify everything is correct.', 'nextText' => 'Continue', 'submitText' => 'Submit Application', ]; $form->createMultistep($steps, $options);
| Option | Type | Default | Description |
|---|---|---|---|
auto |
bool | true |
Auto-generate breadcrumbs, titles, and review page |
reviewTitle |
string | 'Please Review the Information' |
Heading on review page |
reviewText |
string | 'Review Your Information.' |
Instructions on review page |
nextText |
string | 'Next' |
Default button text for steps |
submitText |
string | 'Submit Form' |
Button text on review page |
Helper Methods
For custom UIs, Flick provides helper methods to query the form's state:
// Get the current step name $currentStep = $form->multistepCurrentStep($steps); // "Personal Info" or "Review" // Get all step names including Review. (Review comes from the session, which // createMultistep() seeds — before the first createMultistep() call this // returns only the step names from the $steps array, without Review.) $allSteps = $form->multistepSteps($steps); // ["Personal Info", "Preferences", "Review"] // Get completed steps $completed = $form->multistepCompletedSteps(); // ["Personal Info"] // Check if on the review step if ($form->multistepIsInReview()) { // Show custom review UI } // Check if form is complete if ($form->multistepIsComplete()) { // Process submission } // Get data for custom review (keeps session active) $reviewData = $form->multistepReviewData(); // Generate breadcrumb navigation $form->multistepBreadcrumbs($steps); // Generate the submit button for review page // (pass a second $attributes argument to replace the default button styling) $form->submitMultistep('Finish');
Example: Custom Progress Bar
<?php $allSteps = $form->multistepSteps($steps); $current = $form->multistepCurrentStep($steps); $total = count($allSteps); $currentIndex = array_search($current, $allSteps); $progress = round(($currentIndex / ($total - 1)) * 100); ?> <div class="progress mb-4"> <div class="progress-bar" style="width: <?= $progress ?>%"> Step <?= $currentIndex + 1 ?> of <?= $total ?> </div> </div> <?php $form->createMultistep($steps); ?>
Manual Mode
Set auto => false to take full control of the UI while Flick handles session
management and validation. Manual mode captures the wizard's markup and prints it
where you decide, so it needs 'echo' => false in the Flick config — in the
default echo mode, createMultistep() prints immediately (above your layout) and
returns an empty string.
<?php $form = new Flick\Flick(['views' => 'bootstrap', 'echo' => false]); $options = ['auto' => false]; if ($form->multistepIsComplete()) { $data = $form->multistepFormData(); // Process submission, then stop — falling through would render a // fresh, empty wizard below the success state return; } $output = $form->createMultistep($steps, $options); ?> <div class="wizard-container"> <div class="wizard-header"> <h2><?= htmlspecialchars($form->multistepCurrentStep($steps), ENT_QUOTES, 'UTF-8') ?></h2> <?= $form->multistepBreadcrumbs($steps) ?> </div> <div class="wizard-body"> <?php if ($form->multistepIsInReview()): ?> <h3>Review Your Answers</h3> <dl class="row"> <?php foreach ($form->multistepReviewData() as $key => $value): ?> <dt class="col-sm-4"><?= ucwords(str_replace('_', ' ', $key)) ?></dt> <dd class="col-sm-8"><?= htmlspecialchars($value) ?></dd> <?php endforeach; ?> </dl> <?= $form->submitMultistep('Complete') ?> <?php else: ?> <?= $output ?> <?php endif; ?> </div> </div>
Manual mode gives you complete layout control while preserving:
- Step validation
- Session management
- Breadcrumb navigation
- Form data persistence
Examples
Registration Wizard
A typical user registration flow:
$registration = [ 'Account' => [ 'text' => 'Create your account credentials.', 'fields' => [ 'email' => [ 'type' => 'email', 'label' => 'Email', 'rules' => ['required', 'email'], ], 'password' => [ 'type' => 'password', 'label' => 'Password', 'rules' => ['required', 'strongPassword'], ], 'password_confirm' => [ 'type' => 'password', 'label' => 'Confirm Password', 'rules' => ['required', 'matches:password'], ], ], ], 'Profile' => [ 'text' => 'Tell us about yourself.', 'fields' => [ 'name' => [ 'label' => 'Full Name', 'rules' => ['required'], ], 'bio' => [ 'type' => 'textarea', 'label' => 'Bio', ], ], ], 'Preferences' => [ 'text' => 'Set your preferences.', 'fields' => [ 'newsletter' => [ 'type' => 'checkbox', 'label' => 'Receive newsletter', 'value' => 'yes', ], 'theme' => [ 'type' => 'select', 'label' => 'Theme', 'options' => [ 'light' => 'Light', 'dark' => 'Dark', 'auto' => 'System Default', ], ], ], 'button' => ['text' => 'Create Account'], ], ];
Survey Form
A survey with different question types:
$survey = [ 'About You' => [ 'text' => 'Help us learn about you.', 'fields' => [ 'age_range' => [ 'type' => 'select', 'label' => 'Age Range', 'options' => [ '' => 'Select...', '18-24' => '18-24', '25-34' => '25-34', '35-44' => '35-44', '45-54' => '45-54', '55+' => '55+', ], 'rules' => ['required'], ], 'employed' => [ 'type' => 'radio', 'name' => 'employment', 'label' => 'Employed', 'value' => 'employed', ], 'student' => [ 'type' => 'radio', 'name' => 'employment', 'label' => 'Student', 'value' => 'student', ], 'other_status' => [ 'type' => 'radio', 'name' => 'employment', 'label' => 'Other', 'value' => 'other', ], ], ], 'Feedback' => [ 'text' => 'Share your thoughts.', 'fields' => [ 'rating' => [ 'type' => 'range', 'label' => 'Satisfaction (1-10)', 'value' => '5', 'attributes' => ['min' => 1, 'max' => 10], ], 'comments' => [ 'type' => 'textarea', 'label' => 'Additional Comments', ], ], 'button' => ['text' => 'Submit Survey'], ], ];
Order Form
Shipping and billing addresses:
$order = [ 'Shipping' => [ 'text' => 'Where should we send your order?', 'fields' => [ 'ship_name' => [ 'label' => 'Full Name', 'rules' => ['required'], ], 'ship_address' => [ 'label' => 'Street Address', 'rules' => ['required'], ], 'ship_city' => [ 'label' => 'City', 'rules' => ['required'], ], 'ship_state' => [ 'type' => 'select', 'label' => 'State', 'options' => 'states', 'rules' => ['required'], ], 'ship_zip' => [ 'label' => 'ZIP Code', 'rules' => ['required', 'exact:5', 'integer'], ], ], ], 'Billing' => [ 'text' => 'Enter your billing information.', 'fields' => [ 'same_as_shipping' => [ 'type' => 'checkbox', 'label' => 'Same as shipping address', 'value' => 'yes', ], 'bill_name' => ['label' => 'Full Name'], 'bill_address' => ['label' => 'Street Address'], 'bill_city' => ['label' => 'City'], 'bill_state' => [ 'type' => 'select', 'label' => 'State', 'options' => 'states', ], 'bill_zip' => [ 'label' => 'ZIP Code', 'rules' => ['exact:5', 'integer'], ], ], 'button' => ['text' => 'Proceed to Review'], ], ];
API Reference
For the complete method reference, see the Multistep Helpers in the API documentation.