FlowPowerUser

Form Steps

Stabledata-fpu-form · v1.0.0 · 10 kB gzip · free

Split a Webflow form into several steps — with branching, your own error messages and choice cards.

View live demoPlaceholderGet the cloneablePlaceholderSource on GitHub

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 → Footer Code. The script loads deferred and only starts when it finds a data-fpu attribute on the page.

Footer code · before </body>
<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          data-fpu-form-element="form"
└─ Form
   ├─ Div  Schritt 1   data-fpu-form-element="step"
   ├─ Div  Schritt 2   data-fpu-form-element="step"
   ├─ Div  Schritt 3   data-fpu-form-element="step"
   └─ Div  Knopfleiste
      ├─ Button        data-fpu-form-element="prev"
      ├─ Button        data-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  Wer bist du?  data-fpu-form-element="step"
                  data-fpu-form-step-id="art"

Div  Firmendaten   data-fpu-form-element="step"
                  data-fpu-form-step-if="kundenart=firma"

Div  Privatdaten   data-fpu-form-element="step"
                  data-fpu-form-step-if="kundenart=privat"

Div  Absenden      data-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 form

Form block or form
AttributeValueDescription
data-fpu-form-elementformSwitches the solution on for this form. Works on the form block or on the form itself. Required.

On every step

Div block inside the form
AttributeValueDescription
data-fpu-form-elementstepTurns the div block into a step. DOM order is the order of the steps. Required.
data-fpu-form-step-nameKontaktDisplay name of the step. Appears in {name} and in elements with step-name.
data-fpu-form-step-idkontaktStable ID. Needed for jumps and conditions, recommended as soon as the order can still change.

On the buttons

Button · link block · submit
AttributeValueDescription
data-fpu-form-elementnextNext. Validates the visible step first and stays put if something is missing. Required.
data-fpu-form-elementprevBack. No validation, and it follows the path actually taken — even after a jump.
data-fpu-form-elementsubmitSubmit. Only active on the last step. Without this attribute the solution manages the form's native submit button.
Result in the markup
<form data-fpu-form-element="form">
  <div data-fpu-form-element="step" data-fpu-form-step-name="Kontakt">
    <input name="e-mail" type="email" required>
  </div>

  <div data-fpu-form-element="step" data-fpu-form-step-name="Projekt">
    <textarea name="projekt" required></textarea>
  </div>

  <button data-fpu-form-element="prev">Zurück</button>
  <button data-fpu-form-element="next">Weiter</button>
  <input type="submit" value="Absenden">
</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 step

Condition and jump target
AttributeValueDescription
data-fpu-form-step-ifkundenart=firmaThe step only belongs to the path when the expression is true. Field name on the left, value on the right.
data-fpu-form-step-idfirmaTarget ID for jumps. Without an ID the step's position counts.
data-fpu-form-gotodankeDefault target after this step. Overridden by a target on the button or on the answer.

On the answer

Radio · checkbox · option · next button
AttributeValueDescription
data-fpu-form-gotofirmaIf this answer is selected, next goes straight to the step with that ID. On the next button the target always applies.

Expressions in step-if

same syntax as data-fpu-condition
AttributeDescription
plan=proEquality, case-insensitive, numbers compared as numbers.
plan!=basicInequality.
plan=basic|proSeveral allowed values for the same field.
menge>=5Numeric comparison, also with <, > and <=. „19,99“ is understood.
newsletterJust the field name: true as soon as the field is ticked or filled.
plan=pro && menge>=5AND — both parts must hold.
plan=pro || plan=teamOR — binds weaker than AND, so it groups the AND parts.
Three answers, three paths
<!-- Schritt 2: die Auswahl -->
<div data-fpu-form-element="step" data-fpu-form-step-id="auswahl">
  <label><input type="radio" name="ziel" value="website"> Website</label>
  <label><input type="radio" name="ziel" value="shop">    Onlineshop</label>
  <label><input type="radio" name="ziel" value="support"> Support</label>
</div>

<!-- Danach kommt nur, was zur Antwort passt -->
<div data-fpu-form-element="step" data-fpu-form-step-if="ziel=website">…</div>
<div data-fpu-form-element="step" data-fpu-form-step-if="ziel=shop">…</div>
<div data-fpu-form-element="step" data-fpu-form-step-if="ziel=support">…</div>

<!-- Immer am Ende -->
<div data-fpu-form-element="step" data-fpu-form-step-id="absenden">…</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 runs

On the form or on a single step
AttributeValueDefaultDescription
data-fpu-form-validatestepstepClicking next validates the visible step.
data-fpu-form-validateliveAdditionally when leaving a field — the error appears at once, not only at the end of the step.
data-fpu-form-validatesubmitOnly on submit, but then across every step of the path taken.
data-fpu-form-validatenoneNo validation of our own. Useful for a single intermediate step without fields.

Where the error appears

Text block inside the form
AttributeValueDescription
data-fpu-form-elementerrorCarries the error text. Hidden by default, gets the class fpu-form-error-visible when something is wrong.
data-fpu-form-error-fore-mailBinds the element to a specific field (its name or id) or to a group. Without it, the closest field in the same wrapper applies.
data-fpu-form-elementfieldOptional wrapper around label, field and error. Gets fpu-form-field-invalid on error — handy for a red outline.
data-fpu-form-elementsummarySummary at the top of the step: a list of every error, each entry jumps to its field.

What the error says

On the field, otherwise on the form
AttributeValueDescription
data-fpu-form-message-requiredWir brauchen deinen Namen.Required field is empty. The placeholder {label} inserts the field name.
data-fpu-form-message-emailDa fehlt das @.There is also -url, -tel, -number and -pattern. Set on the form they apply to every field.
data-fpu-form-message-minlengthMindestens {min} Zeichen.Plus -maxlength, -min, -max and -step. {min} and {max} insert the limit.
data-fpu-form-messageBitte prüf das noch mal.One message for every error on this field — handy when a field only has one failure mode.
data-fpu-form-labelE-Mail-AdressePlain-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 data-fpu-form-element="field">
  <label for="mail">E-Mail</label>

  <input id="mail" name="e-mail" type="email" required
         data-fpu-form-label="deine E-Mail"
         data-fpu-form-message-required="Ohne {label} können wir nicht antworten."
         data-fpu-form-message-email="Da fehlt noch das @.">

  <div data-fpu-form-element="error" data-fpu-form-error-for="e-mail"></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, data-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 — data-fpu-form-instance then connects them to the right form.

Display elements

AttributeValueElementDescription
data-fpu-form-elementprogressDiv BlockProgress bar. The width is set in percent, your styling does the rest.
data-fpu-form-elementstep-countText BlockCounter text. Default „Schritt {current} von {total}“.
data-fpu-form-count-template{current}/{total} · {name}Text BlockCustom counter format. On the counter element or on the form. Placeholders: {current}, {total}, {name}.
data-fpu-form-elementstep-nameText BlockShows the name of the current step (data-fpu-form-step-name).
data-fpu-form-elementindicatorDiv BlockOne dot or tab per step, in DOM order. Clickable, but only to steps already visited. Classes: fpu-form-indicator-active, -visited, -locked.
data-fpu-form-indicator-forfirmaDiv 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 data-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
data-fpu-form-elementchoiceThe 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
data-fpu-form-elementgroupCombines the answers into one unit. Only then can a choice be required and get its own error text.
data-fpu-form-requiredtrueAt least one choice. Next only works after that.
data-fpu-form-min2Minimum number of ticks for checkbox groups.
data-fpu-form-max3Maximum number of ticks for checkbox groups.
data-fpu-form-groupinteressenName of the group. An error element with data-fpu-form-error-for=„interessen“ then shows its message.
data-fpu-form-message-group-requiredSuch dir eins aus.Custom text for the group. Plus -group-min and -group-max with {min} and {max}.
data-fpu-form-auto-nexttrueAdvance automatically after a radio choice. Also allowed on the step. The Typeform pattern.
data-fpu-form-auto-next-delay260Delay in milliseconds, so the choice stays visible for a moment.
Required choice with cards
<div data-fpu-form-element="group"
     data-fpu-form-group="ziel"
     data-fpu-form-required="true"
     data-fpu-form-auto-next="true"
     data-fpu-form-label="ein Ziel"
     data-fpu-form-message-group-required="Bitte wähl aus, worum es geht.">

  <label data-fpu-form-element="choice">
    <input type="radio" name="ziel" value="website" data-fpu-form-goto="website">
    <span>Neue Website</span>
  </label>

  <label data-fpu-form-element="choice">
    <input type="radio" name="ziel" value="shop" data-fpu-form-goto="shop">
    <span>Onlineshop</span>
  </label>

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

On the form

AttributeValueDefaultDescription
data-fpu-form-persisttrue · falsefalseStore 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.
data-fpu-form-scrolltrue · falsefalseScroll to the top of the form on every step change. Almost always right for long steps.
data-fpu-form-instance2Connects elements outside the form — progress, counter, indicators, buttons — to exactly this form. Only needed when two forms sit on one page.
data-fpu-form-loggingtrue · falsefalseOne 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: "Schritt {current} von {total}",
    messages: {
      required: "Das brauchen wir noch.",
      email: "Bitte eine gültige E-Mail.",
    },
  };
</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
fpu-form-step-activeThe currently visible step.
fpu-form-step-skippedA step that branching has currently taken out of the path.
fpu-form-invalidOn the field that failed.
fpu-form-field-invalidOn the field wrapper and on the group.
fpu-form-choice-selectedOn the selected choice card.
fpu-form-btn-disabledOn buttons that cannot do anything in the current step.

Events on document

AttributeDescription
fpu:form-step-changedAfter every change. detail: form, index, total, stepId, stepIndex.
fpu:form-invalidFor every message shown. detail: form, element, message.
fpu:form-validWhen a submit passed validation and is let through.
More capabilitiesAdditional 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.