About eight months ago I shipped an onboarding flow I was genuinely proud of. Clean three-step wizard, progress indicator at the top, every screen laid out with careful visual hierarchy and breathing room. It looked exactly right in Figma. The client was happy. I was happy.
Three weeks after launch, the analytics told a different story. Roughly 62% of new registrants were abandoning somewhere between step two and step three — never completing the flow, never reaching the dashboard. Users who did complete it were spending an average of four and a half minutes on a flow I'd estimated would take ninety seconds.
This post is my full breakdown of what went wrong, what I found when I dug in, and what I rebuilt. If you're designing or evaluating an onboarding sequence right now, I want to save you the embarrassment of shipping something that looks finished but is quietly leaking users the moment it goes live.
The Project Context
The product was a B2B SaaS tool for small logistics companies — think route planning, driver assignment, basic dispatch management. Not a consumer app, so I wasn't dealing with completely cold users. They'd signed up because a sales person had already walked them through a demo. They knew what the product did. What I was building was the configuration onboarding: the steps that took a new account from blank to actually usable.
The three steps were: (1) company profile and timezone, (2) add your first vehicle and driver, (3) set your operating hours and service area. Logical, right? That's roughly the order you'd need this data to do anything useful in the system.
In Figma, with realistic placeholder content, it looked effortless. Each screen had one primary action, clear labels, and a soft progress bar at 33%, 66%, 100%. I felt good about it.
What the Data Actually Showed
The client had Microsoft Clarity already installed (they're on shared cPanel hosting, so I'd set up the snippet through their hosting panel — nothing fancy). I asked for access and spent about two hours watching session recordings.
What I saw was uncomfortable to watch.
On step two — add your first vehicle and driver — users were hitting a combined form that asked for vehicle plate number, vehicle type (from a dropdown), driver name, driver phone number, and driver license number. Five fields. All required. And two of those fields had format constraints I had communicated only via placeholder text.
The phone number field expected a specific Indonesian format: 08XX-XXXX-XXXX. The license number expected exactly twelve characters. If you entered anything else and hit Next, you got a red border and a generic message that said "Format tidak valid" — "Invalid format." That was it. No hint of what format was valid. No example. No inline guidance.
I'd written about this exact failure mode in a broader context before — the way error states without genuine guidance just create friction loops rather than helping users recover — but I had somehow shipped exactly that mistake here. The irony was not lost on me. (If you want the full thinking on that, see my post on error states and form validation redesign.)
Users were attempting the phone field, getting an error, trying a different format, getting another error, trying again with spaces removed, still failing — and then leaving. Not frustrated-leave, more like quietly-give-up-leave. Session after session showed this exact pattern. Some users spent over three minutes on that one field alone.
The Second Problem: Forced Commitment Too Early
Here's the thing I hadn't anticipated: the people doing onboarding were often not the people who had this information.
The account owner — usually an operations manager or a business owner — would start onboarding. They'd breeze through step one (company name, city, timezone — they know that). Then they'd hit step two and realise they didn't have the driver's license number memorised. Or the exact vehicle plate. So they'd stop. Completely.
My flow had no "skip for now" option. No "save progress and continue later." You either completed the step in full or you went nowhere. I had designed it this way intentionally — I didn't want users reaching the dashboard with incomplete configuration, because then features would fail silently. The logic was sound. The execution was wrong.
Progressive disclosure is supposed to reduce overwhelm by revealing complexity gradually. But I'd confused it with forced linear completion. Those are not the same thing. Progressive disclosure should let users build context incrementally; it should not trap them at a gate until they produce exact information on demand.
The Third Problem: No Re-entry State
Some users did close the tab and come back later. When they returned, they were dropped at the beginning of step one again. No saved state. No "welcome back, pick up where you left off." The progress bar reset to 33% even though their company profile data was still in the database — they just couldn't see that.
This was a PHP session issue compounded by a UX decision. I was storing the step progress in $_SESSION, which expired after a default timeout on the shared hosting configuration. But even if the session had persisted, I had no logic to detect a partially completed onboarding and route returning users to their correct step. I'd simply never designed for the returning-incomplete-user state.
The Rebuild
1. Format guidance moved inline, before errors
The phone and license fields now show a soft helper text beneath the label on focus — not on error, on focus. "Format: 08XX-XXXX-XXXX" appears the moment the cursor enters the field. That's it. The error state still fires on incorrect submission, but now users aren't encountering format requirements for the first time when they've already failed.
2. Optional fields become optional
Driver license number is now marked optional during onboarding, with a small tooltip explaining it's required before that driver can be assigned a route. This was a product decision as much as a UX decision, and I had to push back on the initial instinct to just keep it required. Making it optional didn't break anything — the system simply flags the driver as "incomplete profile" and prevents assignment until it's filled in later. The user reaches the dashboard, sees the product work, feels momentum. Much better than losing them entirely.
3. Progress persistence moved to the database
I added an onboarding_step column to the users table — just an integer, 1 through 4 (I added a final "done" state). Every time a user completes a step, that integer updates. On login, before routing to the dashboard, the PHP checks this value. If it's below 4, the user gets routed to the onboarding flow at their correct step, not the beginning. Sessions can expire; the database doesn't lie.
4. A genuine save-and-exit path
Each step now has a "Save and finish later" link, visually de-emphasised but present. It commits whatever valid data exists to the database, increments the step marker, and takes the user to a simplified dashboard with a prominent "Complete your setup" banner. That banner doesn't go away until onboarding is finished — and it tells them exactly what's missing, with a direct link back to the relevant step.
The Results, Honestly Framed
Two weeks after the rebuild went live, step-two abandonment dropped from 62% to roughly 19%. Average time on step two went from four and a half minutes to under a minute. Those aren't perfect numbers — 19% is still real drop-off — but the remaining abandonment is mostly users who never returned after their first session, which is a different problem and not one onboarding UI alone can solve.
What struck me most in the follow-up Clarity recordings was how calm the sessions looked. Users tabbed through fields, hit Next, moved on. No looping, no error spirals, no rage-closes. It looked exactly like the happy path I'd originally designed for — except now users who hit snags had somewhere to go instead of nowhere.
What I'd Tell Myself Before the First Build
- Design for re-entry from day one. Any onboarding flow that spans more than a single session needs a durable persistence strategy. Sessions are volatile; database state is not.
- Placeholder text is not documentation. Format hints belong in persistent helper text, not in placeholders that vanish the moment someone clicks into the field.
- Required vs. blocking are different concepts. A field can be required for a feature to work without being required to progress through onboarding. Know which threshold you actually need to enforce at each step.
- Test with non-ideal data. I ran usability tests with my own phone number, which I typed correctly the first time. Of course it passed. Test with the wrong format. Test with a copy-pasted number that has spaces. Test with someone who has to leave the room and come back.
- Completion rate is a lagging indicator. By the time completion rate looks bad, you've already lost a lot of users. Watch time-on-step and error event frequency as leading signals, not completion as a trailing one.
Closing Thought
The hardest part of this post-mortem wasn't the technical fixes — the database column, the routing logic, the helper text. Those were genuinely straightforward once I understood the actual problem. The hard part was accepting that I had shipped a flow that looked correct and was wrong in the ways that matter most: it didn't account for real humans with incomplete information, fragile attention, and a real-world context I hadn't bothered to fully imagine.
Good onboarding design isn't about making a beautiful wizard. It's about making a resilient path — one that bends without breaking when users don't behave the way your Figma prototype assumed they would. The beautiful wizard part is easy. The resilient path part is where the actual design work lives.
Frequently Asked Questions
What is progressive disclosure in UX design, and why does it matter for onboarding?
Progressive disclosure means revealing information and options only as users need them, reducing cognitive load at each step. In onboarding, it prevents overwhelming new users by breaking setup into contextually relevant chunks rather than front-loading every feature and configuration option at once.
How do you measure whether an onboarding flow is actually working?
Track step-level drop-off rates individually, not just overall completion — a 60% completion rate can hide a single catastrophic step causing 80% of exits. Pair funnel analytics with session recordings (tools like Hotjar or Microsoft Clarity) to see exactly where hesitation and abandonment happen, then correlate with time-on-step data.
What's the most common mistake designers make when designing onboarding sequences?
Designing for the happy path only. Most onboarding flows are prototyped assuming users arrive motivated, with all required information at hand, and zero distractions. Real users skip steps, leave and return, paste invalid data, and hit edge cases — and when the flow has no graceful recovery for those moments, they simply leave.