Bootcamp
Build a multi-step onboarding wizard in Bubble
Photo by George Bakos on Unsplash
Sooner or later every app needs an onboarding flow: a sequence of screens that welcomes a new user, collects their details a step at a time, shows their progress, and ends with a warm “thanks for joining us”. The obvious way to build one is also the worst way. Hard-code five screens, fifteen buttons and a tangle of show/hide workflows, and the whole thing shatters the day your client says “actually, I really need to capture one more field”, and a new step has to squeeze into the middle.
There is a better pattern, inspired by Airbnb’s onboarding and set-up-a-property flows: one page, one reusable step template, and data deciding what shows.
A multi-step form wizard is a single page that reveals one step at a time: a shared template supplies the layout, a URL parameter tracks where the user is, and an option set supplies each step’s content, progress and navigation rules. You add a step later by adding a row of data, not by rebuilding the page.
Plan before you touch the editor
Look at the wireframes for a standard onboarding flow and five questions fall out. When is data created and updated, and where in the flow do we create the profile record, populate its fields and finally publish it? How do we show and hide the different steps: is this a multi-page app or a single-page app? How do we navigate up and down the sequence? How will we display progress? And how do we tell whether each step is complete when we reach the review screen?
Jot answers on paper before building anything. This article covers the structure and navigation; the data questions get their own treatment in wiring the onboarding flow to your database.
One observation drives the whole design. In a typical flow of intro, profile, company, review and welcome screens, the middle three share an identical template: header, progress bar, a left side holding a heading and a form, a right side holding a help card, and a nav pinned to the bottom. Only the first and last screens are different. So we build the template once and imagine it stretching to five or ten data-collection steps without new layout work.
Build the step template
- Add a new page called
onboarding, cloning it from an existing page so you inherit the header. Delete what you don’t need (the nav drawer, spare content groups) and keep a clean hierarchy: a full-width group for the header, then the main content. - Reuse the security-container pattern: wrap the page content in a
Group Security Containerthat is not visible on page load, collapses when hidden, and has a conditional making it visible only for logged-in users. It should expand to the full page width. - Install the Progress Bar plugin from the Plugins tab (it’s made by Bubble, and like most Bubble plugins it does one thing well). Drop the element immediately under the header, not inside a padded content group, so it runs edge to edge. Give it a fixed height of 10px, the primary colour for the bar, your primary-100 shade for the track, no rounding and no borders. The plugin insists on showing a percentage label, so set that value to zero as a workaround to hide it.
- Constrain the content the same way as the header: an outer group spanning the page with 156px of horizontal padding, and an inner
Group Contentwith a max width of 1128, centred in its parent. Setting both might feel like doing the same thing twice, but it’s what keeps the layout correct when the viewport goes wider or narrower. - Make
Group Contenta row and add two children.Group Left(max width 552) holds aGroup Left Headingwith a Heading 2 (“Great, let’s get you started”) and a body-large subheading, separated by an 8px gap, then aGroup Formbelow with a 32px gap between heading and form. Drop a placeholder shape in the form group for now. Group Right(max width 456) is the help card: 8px corner rounding, a solid 1px gray-200 border, and a subtle outset drop shadow (4 / 12 / 20). Inside, a bold title, a body-large description, and a 32px icon (a light bulb works) with a circular border, which means a roundness of 16, half of 32. Give the card 16px padding and an 8px gap. OnGroup Content, set the container alignment to space between, so the gap between left and right breathes as the page resizes rather than being fixed.- Build the nav in a Floating Group floated relative to the bottom of the screen, full width, with 52px of bottom padding. Inside it, a
Group Containerwith max width 1128, centred, at a fixed 48px height. Add three buttons at 168px wide:Skip(tertiary),Back(secondary) andNext(primary), grouped into a row container with a 24px gap and aligned right. - Give every button proper states: an “isn’t clickable” condition (gray-100 background, gray-300 text) and a hover state. On the tertiary button, don’t just remove the border; set a solid border in white at full transparency. In the CSS box model a border always occupies space, so an invisible border keeps the button from jumping a pixel when states change.
- Finally, add the two odd screens as siblings of the steps template:
Group Content Start(a dark primary-coloured panel with a white subheading, 64px vertical and 72px horizontal padding, 8px rounding) andGroup Content End(a centredGroup Message, max width 684, with a heading, body text and a “Start exploring” button). Both default to hidden and collapse when hidden.
Throughout, keep checking the element tree. Everything is boxes, rows and columns, and nearly every layout bug comes from an element sitting one level away from where you think it is.
Wire the navigation with a URL parameter
Store the current step in the URL rather than a custom state, and a browser reload won’t lose the user’s position.
- On the
Nextbutton, add a workflow with Go to page. Use theCurrent pageexpression for the destination (a good habit, and essential inside reusable elements where you can’t know the page). Send an additional parameter calledpage, with the valueGet page from URL:formatted as number + 1, using Get data from URL with the keypageand the type set to number. - Copy that workflow onto
Backand change the expression to- 1.Skipgets the same+ 1as Next. - Preview and click. The parameter climbs 1, 2, 3 and keeps going forever; there are no restrictions yet, and that’s fine. The constraints come from data in the next section.
Power the steps with an option set
Numbers control the order, but numbers shift when a step is inserted, so we also want stable names. An option set gives us both.
- Create an option set called
Onboarding Step OSwith optionsstart,profile,company,reviewandend. Add a number attribute calledsort byand set it to 1 through 5. This associates each name with a number, so your logic can reference names while the ordering stays editable. - On
Group Content Start, set not visible on page load and collapse when hidden, then add a conditional: whenGet page from URL (page, as number) is Onboarding Step OS's start's sort by, this element is visible. Do the same forGroup Content Endwith theendoption. Group Content Steps(the shared middle template) shows when the page number is not start’s sort by and is not end’s sort by. Test it and you’ll hit a classic logic trap: with no parameter at all, both “is not” checks evaluate true on an empty value and the steps appear on a blank URL. Add a third clause,and Get page from URL (page, as number) is not empty. When an expression misbehaves, enumerate every possibility, including empty.- Handle the blank URL properly with a redirect: a Page is loaded event, only when the
pageparameter is empty, that goes to the current page withpage=1. - Inside
Group Form, add one group per middle step:Group Profile Form,Group Company Form,Group Review. Each is hidden and collapsed by default, with a conditional such asGet page from URL (page, as number) is Onboarding Step OS's profile's sort by. Once the first is built, copy the condition and change one word per group.
Dynamic titles, help cards and progress
Rather than duplicating headings and cards per step, put the content on the option set too.
- Add text attributes:
step title,subheading,help card titleandhelp card description. Fill them in for the middle steps (“Why do we need this information?”, “Please complete the input fields below with your details”, and so on). - In the help card’s title text, insert dynamic data with this expression, written slightly backwards:
All Onboarding Step OS:filteredwith the constraintsort by = Get page from URL (page, as number), then:first item's help card title. You retrieve the whole list, filter it to the one matching the URL, and pull the attribute. Bubble still treats a filtered list as a list even when you know only one option can match, so:first itemconverts it to a single item. You will use that move constantly. - Reuse the same expression for the description, the step title and the subheading, changing only the final attribute. Where a step has no subheading, leave the attribute blank: with fit height to content and no min height on the text element, an empty value occupies no space at all.
- A small personalisation trick: append
:find & replaceto the step title expression, find the textname, and replace it withCurrent User's first name. The company step’s title “Tell us about yourself, name” renders as “Tell us about yourself, Will”. - For progress, add a number attribute called
progress bar(say 5, 20, 40, 80, 100) and feed the same filtered expression into the plugin’s percentage. Or compute it:Get page from URL (page, as number) / 5 * 100. The division alone returns a fraction like 0.4 and the plugin needs integers, hence the times 100. The computed version means adding a sixth step is just changing the 5 to a 6.
Show and hide the nav buttons
Right now Skip, Back and Next show on every step and the user can click past the end. The buttons should be data-driven too.
- Add yes/no attributes to the option set:
show next button,show back button,show skip button, plus a text attributenext button label. - On each button, add a conditional using the familiar expression:
All Onboarding Step OS:filtered (sort by = Get page from URL):first item's show next buttonis yes, then visible. Untick visible on page load and collapse when hidden so hidden is the default. - Set the values: the start shows only Next (labelled “Continue”); profile and company show all three (“Next”); review shows Back and Next (“Create profile”); the end shows none.
- Make the Next button’s text dynamic with the same expression ending in
next button label.
Step back and look at what you have. No step is hard-coded. The option set is pure content data, which is exactly what option sets are for: it loads fast, contains nothing sensitive, and rarely changes. When requirements change, you create a new option, set its attributes, shuffle the sort-by numbers, and every conditional, title, help card, progress value and button updates itself.
Try it yourself
Add a “preferences” step between company and review. Create the option, give it sort by 4, push review and end to 5 and 6, fill in its titles and button flags, and add one form group with the matching conditional. If the progress bar, nav buttons and help card all just work, you’ve built the pattern correctly. If anything needs hand-editing, find the hard-coded value and replace it with the option set expression.
This skill in the AI era
The instinct this build trains, separating content from structure so data drives the interface, is precisely the instinct that transfers to modern development. A wizard in a code stack is a config array and one step component, which is the same design with different syntax. When we run Claude Code training for teams, the builders who learned to think this way in Bubble specify features as data plus rules, and the AI writes clean implementations first time. The tools changed more than the thinking did; we’ve written up what carried over from Bubble to AI coding.
If this sparked something, let's talk.
No pitch, no pressure — just a conversation about what you're working on.
Let's talkRelated posts
Bootcamp
A simple approach to data modelling
A four-step method for designing a database schema: identify entities, map relationships, choose fields. Taught through the CRM data model built in Bubble.
Bootcamp
Module 5 — Application architecture: pages, states and reusable pieces
Single-page vs multi-page apps, URL parameters, custom states, reusable elements — and a big practical building a main template with an expanding navigation drawer.
Bootcamp
API calls as actions vs data (and the planning checklist)
When to use Bubble API calls as actions versus data sources, a one-to-many auth pattern, and the planning checklist that keeps integrations from failing silently.