batistackapp/progress-stepper
A Laravel package for building progress steppers with a clean API for defining steps, tracking current progress, and rendering consistent multi-step workflows in PHP applications.
This is the first stable tagged release (1.0.0) of BatistackApp/progress-stepper. The release notes are effectively just a changelog link, so treat this as "API is now declared stable" rather than a feature announcement — read CHANGELOG.md and the README in the 1.0.0 tag before you build on it.
Minimal setup:
composer require batistackapp/progress-stepper:^1.0
php artisan vendor:publish --provider="BatistackApp\ProgressStepper\ProgressStepperServiceProvider"
Then look at, in this order:
config/progress-stepper.php — the published config is the fastest way to learn what the package considers tunable (variants, colours, orientation, default step rendering).resources/views/ (published via --tag=progress-stepper-views) — the Blade markup tells you exactly which slots and attributes exist, which is usually faster than reading the readme.boot()/register() — confirms the component namespace and whether anything is registered globally (Blade component, Livewire component, view namespace).First use case: drop a stepper into a multi-step form or onboarding flow where the step state already lives somewhere (a controller session key, a Livewire property, or a ?step= query param) and you just need the visual indicator.
<x-progress-stepper
:steps="['Account', 'Company', 'Billing', 'Review']"
:current="3"
/>
If the component name above doesn't resolve, check the provider for the registered alias prefix (<x-progress-stepper::stepper /> is common for namespaced packages) before assuming the install failed.
Keep step definitions in one place. Define the ordered steps once — a config array, an enum, or a dedicated Step value object — and derive labels from it everywhere. Duplicating the label array between the stepper and the form sections is the most common source of drift when steps get reordered.
// config/onboarding.php
return [
'steps' => [
['key' => 'account', 'label' => 'Account', 'route' => 'onboarding.account'],
['key' => 'company', 'label' => 'Company', 'route' => 'onboarding.company'],
['key' => 'billing', 'label' => 'Billing', 'route' => 'onboarding.billing'],
['key' => 'review', 'label' => 'Review', 'route' => 'onboarding.review'],
],
];
Controller-driven (server-rendered wizard). Store the current step in the session or derive it from the route, then pass an index into the component. Compute "completed" server-side from real model state rather than assuming every step before the current one is done — users skip backwards, and a stepper that shows Billing as complete because they're on Review is a bug report waiting to happen.
$completed = collect(config('onboarding.steps'))
->filter(fn ($step) => $user->hasCompletedStep($step['key']))
->pluck('key')
->all();
return view('onboarding.billing', [
'steps' => config('onboarding.steps'),
'current' => 'billing',
'completed' => $completed,
]);
Livewire-driven. Bind the current step to a public property and let Livewire re-render the stepper; this is the least code and usually the best fit for anything with validation per step. Add wire:key on the stepper wrapper if you swap step content in and out, otherwise Alpine/JS state inside the component can survive a step change it shouldn't.
Progress percentage. If the component exposes a progress mode, compute the percentage from the same step list rather than hardcoding it (round(($index + 1) / count($steps) * 100)) so it stays correct when steps are added.
Partial/embedded usage. For a compact "3 of 5" indicator in a header, pass the same source array to a second instance with a minimal variant instead of writing a separate partial — one source of truth, two presentations.
Integration tip: wrap the stepper in your own Blade component (<x-ui.stepper>) that maps your domain vocabulary (enum cases, model states) onto the package's props. That single wrapper is where you absorb any prop renaming in a 1.x release, instead of touching every view.
1.0.0 is the first stable tag; semver applies from here, but expect prop/shape adjustments in early 1.x minors. Use ^1.0 with a committed composer.lock, and read the changelog before bumping.composer update; published views are yours, but they also won't receive upstream fixes — check the changelog for view changes on each bump.content in tailwind.config.js, or ensure a safelist, then rebuild.current is 0-based or 1-based and write it into your wrapper component's docblock. This is the single most common integration mistake and produces a stepper that's eternally one step behind.@cache) or use response caching on the wizard pages, the stepper can show stale "current" state after a redirect. Keep the stepper out of cached fragments, or key the cache on the step.aria-current="step" and a sensible ordered list (<ol>). If the package emits plain <div>s, add the semantics in your wrapper — it's a small change with a large payoff.php artisan blade:list-style introspection or by grepping the provider, then check that current isn't landing outside the steps range (some implementations render nothing rather than throwing).aria-current, not on class names — visual classes are the most likely thing to change across minor releases.How can I help you explore Laravel packages today?