You can build this with AI, the prompt is ready
View live demoGet the cloneable

Form Steps turns an ordinary Webflow form into a multi-step form. It stays a Webflow form: submit, success and error states, Zapier, Make and every integration keep working. Only what happens between the first field and the submit is new.

  • Branching: the answer in step 2 decides which steps come up at all.
  • Your own error messages instead of the browser bubble, per field or per form.
  • Required fields apply per step: you only move on once the visible step is complete.

Add this <script> to your project's custom code

Once per project is enough, even if you use the solution on several pages: Project Settings → Custom Code → Head Code. The script loads alongside the page without holding up rendering; it runs once the page is in place and does nothing while no fpu attribute is there.

<!-- FlowPowerUser · Form Steps -->
<script defer src="https://cdn.jsdelivr.net/npm/@flowpoweruser/attributes@1/form-steps.js"></script>

Choose how the form is built

Every step is an ordinary div block inside the form. You build all steps below each other in the Designer — only one is ever visible. Two ways are common:

Linear — every step comes up

Recommended to start
Form Block          fpu-form-element="form"
└─ Form
   ├─ Div  Step 1      fpu-form-element="step"
   ├─ Div  Step 2      fpu-form-element="step"
   ├─ Div  Step 3      fpu-form-element="step"
   └─ Div  Button row
      ├─ Button        fpu-form-element="prev"
      ├─ Button        fpu-form-element="next"
      └─ Submit

Three to five steps in a fixed order. Two attributes and two buttons, nothing more.

Branched — the answer decides

For funnels
Div  Who are you?  fpu-form-element="step"
                   fpu-form-step-id="type"

Div  Company data  fpu-form-element="step"
                   fpu-form-step-if="customer=company"

Div  Private data  fpu-form-element="step"
                   fpu-form-step-if="customer=private"

Div  Submit        fpu-form-element="step"

Each step gets an ID and a condition. Pick “company” and you see the company steps; pick “private” and they are skipped. Progress and counter automatically use the shorter path.

Add these required attributes to the elements

On the formForm block or form
AttributeValueDescription
Switches the solution on for this form. Works on the form block or on the form itself. Required.
On every stepDiv block inside the form
AttributeValueDescription
Turns the div block into a step. DOM order is the order of the steps. Required.
Display name of the step. Appears in {name} and in elements with step-name.
Stable ID. Needed for jumps and conditions, recommended as soon as the order can still change.
On the buttonsButton · link block · submit
AttributeValueDescription
Next. Validates the visible step first and stays put if something is missing. Required.
Back. No validation, and it follows the path actually taken — even after a jump.
Submit. Only active on the last step. Without this attribute the solution manages the form's native submit button.
Result in the markup
<form fpu-form-element="form">
  <div fpu-form-element="step" fpu-form-step-name="Contact">
    <input name="email" type="email" required>
  </div>

  <div fpu-form-element="step" fpu-form-step-name="Project">
    <textarea name="project" required></textarea>
  </div>

  <button fpu-form-element="prev">Back</button>
  <button fpu-form-element="next">Next</button>
  <input type="submit" value="Submit">
</form>

Branch: which steps follow which answer

Two ways lead there, and they can be mixed. A condition on the step decides whether the step is part of the path at all. A jump target on the answer sends the user straight to a specific step. Skipped steps count in neither progress nor counter, are not validated, and their fields are disconnected — so they never reach the submit either.

On the stepCondition and jump target
AttributeValueDescription
The step only belongs to the path when the expression is true. Field name on the left, value on the right.
Target ID for jumps. Without an ID the step's position counts.
Default target after this step. Overridden by a target on the button or on the answer.
On the answerRadio · checkbox · option · next button
AttributeValueDescription
If this answer is selected, next goes straight to the step with that ID. On the next button the target always applies.
Expressions in step-ifsame syntax as fpu-condition
AttributeDescription
Equality, case-insensitive, numbers compared as numbers.
Inequality.
Several allowed values for the same field.
Numeric comparison, also with <, > and <=. Both “19.99” and the German “19,99” are understood.
Just the field name: true as soon as the field is ticked or filled.
AND — both parts must hold.
OR — binds weaker than AND, so it groups the AND parts.
Three answers, three paths
<!-- Step 2: the choice -->
<div fpu-form-element="step" fpu-form-step-id="choice">
  <label><input type="radio" name="goal" value="website"> Website</label>
  <label><input type="radio" name="goal" value="shop">    Online shop</label>
  <label><input type="radio" name="goal" value="support"> Support</label>
</div>

<!-- Only what matches the answer comes next -->
<div fpu-form-element="step" fpu-form-step-if="goal=website">…</div>
<div fpu-form-element="step" fpu-form-step-if="goal=shop">…</div>
<div fpu-form-element="step" fpu-form-step-if="goal=support">…</div>

<!-- Always last -->
<div fpu-form-element="step" fpu-form-step-id="submit">…</div>

Your own error messages instead of the browser bubble

Out of the box the solution validates every visible field of the step on next and shows the browser bubble. As soon as you place an error element anywhere in the form, the text moves there — styled in the Designer, visible only when something is wrong. What gets validated still comes from the usual Webflow field settings (required, type, pattern, length).

When validation runsOn the form or on a single step
AttributeValueDefaultDescription
stepClicking next validates the visible step.
Additionally when leaving a field — the error appears at once, not only at the end of the step.
Only on submit, but then across every step of the path taken.
No validation of our own. Useful for a single intermediate step without fields.
Where the error appearsText block inside the form
AttributeValueDescription
Carries the error text. Hidden by default, gets the class fpu-form-error-visible when something is wrong.
Binds the element to a specific field (its name or id) or to a group. Without it, the closest field in the same wrapper applies.
Optional wrapper around label, field and error. Gets fpu-form-field-invalid on error — handy for a red outline.
Summary at the top of the step: a list of every error, each entry jumps to its field.
What the error saysOn the field, otherwise on the form
AttributeValueDescription
Required field is empty. The placeholder {label} inserts the field name.
There is also -url, -tel, -number and -pattern. Set on the form they apply to every field.
Plus -maxlength, -min, -max and -step. {min} and {max} insert the limit.
One message for every error on this field — handy when a field only has one failure mode.
Plain-text name for {label}. Without it the label is used, otherwise the placeholder, otherwise the field name.
A field with its own error text
<div fpu-form-element="field">
  <label for="mail">Email</label>

  <input id="mail" name="email" type="email" required
         fpu-form-label="your email"
         fpu-form-message-required="Without {label} we cannot reply."
         fpu-form-message-email="The @ is still missing.">

  <div fpu-form-element="error" fpu-form-error-for="email"></div>
</div>

Publish the project and click through the form on the live site

Attributes only take effect on the published page — in the Designer the stack of steps stays visible, and that is correct. If nothing happens, fpu-form-logging=“true” on the form helps: the console then reports in one line how many steps were found and how many of them are currently part of the path.

OptionalOnly needed when you need it. Expand
OptionalProgress, counter and indicatorsBar, “step 2 of 4”, dot row

All three use the current path: if a step drops out through branching, it counts nowhere. The elements may also sit outside the form — fpu-form-instance then connects them to the right form.

Display elements

AttributeValueElementDescription
Div BlockProgress bar. The width is set in percent, your styling does the rest.
Text BlockCounter text. The built-in default is German — “Schritt {current} von {total}” — so set your own here for an English site.
Text BlockCustom counter format. On the counter element or on the form. Placeholders: {current}, {total}, {name}.
Text BlockShows the name of the current step (fpu-form-step-name).
Div BlockOne dot or tab per step, in DOM order. Clickable, but only to steps already visited. Classes: fpu-form-indicator-active, -visited, -locked.
Div BlockBinds an indicator to a step ID. Without it the position counts, and surplus indicators are hidden.
OptionalCustom choice elements: radios, checkboxes, dropdownsCards, group requirement, auto-advance

Webflow can style radios and checkboxes, but it cannot make a whole card clickable or enforce “at least one choice”. That is exactly what this adds. For styled dropdowns the native select stays the form field — combine it with fpu-select (Custom Form Select); focus and error marking then land on the visible trigger automatically.

Choice card

Div block with a radio or checkbox inside
AttributeValueDescription
The whole surface becomes clickable. When selected it carries fpu-form-choice-selected — your styling for the chosen state. If the input is visually hidden, the card takes over focus and keyboard.

Group of radios or checkboxes

Div block around all answers
AttributeValueDescription
Combines the answers into one unit. Only then can a choice be required and get its own error text.
At least one choice. Next only works after that.
Minimum number of ticks for checkbox groups.
Maximum number of ticks for checkbox groups.
Name of the group. An error element with fpu-form-error-for=“interests” then shows its message.
Custom text for the group. Plus -group-min and -group-max with {min} and {max}.
Advance automatically after a radio choice. Also allowed on the step. The Typeform pattern.
Delay in milliseconds, so the choice stays visible for a moment.
Required choice with cards
<div fpu-form-element="group"
     fpu-form-group="goal"
     fpu-form-required="true"
     fpu-form-auto-next="true"
     fpu-form-label="a goal"
     fpu-form-message-group-required="Please pick what this is about.">

  <label fpu-form-element="choice">
    <input type="radio" name="goal" value="website" fpu-form-goto="website">
    <span>New website</span>
  </label>

  <label fpu-form-element="choice">
    <input type="radio" name="goal" value="shop" fpu-form-goto="shop">
    <span>Online shop</span>
  </label>

  <div fpu-form-element="error" fpu-form-error-for="goal"></div>
</div>
OptionalMore settingsPersistence, scrolling, several forms

On the form

AttributeValueDefaultDescription
falseStore entries and the step reached for the session and restore them on return. Passwords, files and hidden fields are never stored; everything is cleared after a successful submit.
falseScroll to the top of the form on every step change. Almost always right for long steps.
Connects elements outside the form — progress, counter, indicators, buttons — to exactly this form. Only needed when two forms sit on one page.
falseOne line in the console on start: version, number of steps, validation mode.
Global defaults for every form
<script>
  window.fpuFormStepsConfig = {
    persist: true,
    scroll: true,
    validate: "live",
    countTemplate: "Step {current} of {total}",
    messages: {
      required: "We still need this.",
      email: "Please use a valid email address.",
    },
  };
</script>
OptionalKeyboard, classes and eventsfor animations and your own script

Enter in a field means next instead of submit, except on the last step. Alt+arrow right and left page through, as long as focus is not in a text field or dropdown. After every change focus jumps to the first field of the new step.

Classes you can style

AttributeDescription
The currently visible step.
A step that branching has currently taken out of the path.
On the field that failed.
On the field wrapper and on the group.
On the selected choice card.
On buttons that cannot do anything in the current step.

Events on document

AttributeDescription
After every change. detail: form, index, total, stepId, stepIndex.
For every message shown. detail: form, element, message.
When a submit passed validation and is let through.
More capabilities

Additional features

Webflow submit stays untouched
Validation runs before Webflow's own handler. Invalid or premature submits never reach Webflow — success state, error state and every connected service behave as usual.
Back follows the real path
After a jump, back leads to where you actually came from, not to the previous element in the Designer.
Cleanly removable
window.fpuFormSteps.destroyAll() removes every listener, class and ARIA attribute and restores the initial state — for page transitions and single-page environments.
Accessible out of the box
Steps are named groups, inactive steps are inert and gone for screen readers, progress is a progressbar, errors are announced as alerts and tied to the field via aria-describedby.