Multi-Step & Conversational Forms
Core Forms supports splitting long forms into multiple steps with progress indicators, step-by-step navigation, and per-step validation. You can also use conversational mode for a focused, one-question-at-a-time experience.
Schema Layout Types
Set the layout type in the form schema to enable multi-step or conversational rendering:
{
"layout": {
"type": "multi-step"
}
}
Or for conversational mode:
{
"layout": {
"type": "conversational"
}
}
Defining Steps
Option 1: Group Property on Fields
Assign a group property to each field in your schema. Fields sharing the same group value are rendered together in a single step.
{
"fields": [
{ "name": "first_name", "type": "text", "label": "First Name", "group": "personal" },
{ "name": "last_name", "type": "text", "label": "Last Name", "group": "personal" },
{ "name": "email", "type": "email", "label": "Email", "group": "contact" },
{ "name": "phone", "type": "tel", "label": "Phone", "group": "contact" },
{ "name": "message", "type": "textarea", "label": "Message", "group": "details" }
]
}
This produces three steps: personal, contact, and details.
Option 2: Explicit Steps Array
Define steps explicitly with layout.steps for full control over step titles and field ordering:
{
"layout": {
"type": "multi-step",
"steps": [
{ "id": "personal", "title": "Your Info", "fields": ["first_name", "last_name"] },
{ "id": "contact", "title": "Contact", "fields": ["email", "phone"] },
{ "id": "details", "title": "Message", "fields": ["message"] }
]
}
}
Generated HTML Structure
Core Forms renders the following markup for multi-step forms:
<form class="cf-form cf-multi-step" data-layout="multi-step">
<!-- Progress bar -->
<div class="cf-progress" role="progressbar" aria-valuenow="1" aria-valuemin="1" aria-valuemax="3">
<div class="cf-progress-bar" style="width: 33.33%"></div>
<ol class="cf-step-indicators">
<li class="cf-step cf-step-active" data-step="1">
<span class="cf-step-number">1</span>
<span class="cf-step-title">Your Info</span>
</li>
<li class="cf-step" data-step="2">
<span class="cf-step-number">2</span>
<span class="cf-step-title">Contact</span>
</li>
<li class="cf-step" data-step="3">
<span class="cf-step-number">3</span>
<span class="cf-step-title">Message</span>
</li>
</ol>
</div>
<!-- Step panels -->
<div class="cf-step-panel cf-step-panel-active" data-step="1">
<!-- Fields for step 1 -->
</div>
<div class="cf-step-panel" data-step="2" hidden>
<!-- Fields for step 2 -->
</div>
<div class="cf-step-panel" data-step="3" hidden>
<!-- Fields for step 3 -->
</div>
<!-- Hidden input tracking current step -->
<input type="hidden" name="_cf_current_step" value="1">
<!-- Step navigation -->
<div class="cf-step-nav">
<button type="button" class="cf-step-prev" disabled>Previous</button>
<button type="button" class="cf-step-next">Next</button>
<button type="submit" class="cf-step-submit" hidden>Submit</button>
</div>
</form>
Progress Indicators
The .cf-progress container renders both a visual progress bar and numbered step indicators. The progress bar width updates automatically as the user advances:
Step 1 of 3 → width: 33.33%
Step 2 of 3 → width: 66.66%
Step 3 of 3 → width: 100%
Completed steps receive the cf-step-complete class. The active step has cf-step-active.
Step Navigation
Navigation buttons appear in the .cf-step-nav container:
- Previous button is disabled on the first step and hidden in conversational mode.
- Next button advances to the next step after validation passes.
- Submit button replaces Next on the final step.
The hidden _cf_current_step input is updated on every step change so the server knows which step the user reached.
Per-Step Validation
Before advancing to the next step, Core Forms validates all required fields in the current step panel. If any field fails validation:
- The step does not advance.
- Invalid fields receive the
cf-invalidclass witharia-invalid="true". - Error messages appear below each invalid field with
role="alert". - Focus moves to the first invalid field.
// Validation is triggered automatically on Next click.
// Required fields, email format, min/max, and pattern are all checked per step.
// See validation.md for the full list of supported checks.
Conversational Mode
When layout.type is set to conversational, the form renders one question at a time in a centered, focused layout:
<form class="cf-form cf-conversational" data-layout="conversational">
<div class="cf-step-panel cf-step-panel-active cf-conversational-panel" data-step="1">
<label class="cf-conversational-label">What is your name?</label>
<input type="text" name="name" class="cf-conversational-input" required>
</div>
<!-- Additional panels hidden until reached -->
<input type="hidden" name="_cf_current_step" value="1">
<div class="cf-step-nav cf-conversational-nav">
<button type="button" class="cf-step-next">Continue</button>
</div>
</form>
Key differences from standard multi-step:
- No progress bar or step indicators by default (add via filter).
- Each panel is vertically centered on screen.
- Enter key advances to the next question.
- Previous button is hidden (users can click completed indicators to go back).
- Smooth transition animations between questions.
CSS Classes Reference
| Class | Element | Description |
|---|---|---|
.cf-multi-step |
<form> |
Multi-step form wrapper |
.cf-conversational |
<form> |
Conversational form wrapper |
.cf-progress |
<div> |
Progress bar and indicators container |
.cf-progress-bar |
<div> |
Visual progress bar fill |
.cf-step-indicators |
<ol> |
Step indicator list |
.cf-step |
<li> |
Individual step indicator |
.cf-step-active |
<li> |
Currently active step indicator |
.cf-step-complete |
<li> |
Completed step indicator |
.cf-step-panel |
<div> |
Step content panel |
.cf-step-panel-active |
<div> |
Visible step panel |
.cf-step-nav |
<div> |
Navigation buttons container |
.cf-step-prev |
<button> |
Previous step button |
.cf-step-next |
<button> |
Next step button |
.cf-step-submit |
<button> |
Submit button (final step) |
Example: Registration Form
{
"layout": {
"type": "multi-step",
"steps": [
{ "id": "account", "title": "Account", "fields": ["email", "password", "password_confirm"] },
{ "id": "profile", "title": "Profile", "fields": ["first_name", "last_name", "bio"] },
{ "id": "preferences", "title": "Preferences", "fields": ["newsletter", "terms"] }
]
},
"fields": [
{ "name": "email", "type": "email", "label": "Email", "required": true },
{ "name": "password", "type": "password", "label": "Password", "required": true, "minlength": 8 },
{ "name": "password_confirm", "type": "password", "label": "Confirm Password", "required": true },
{ "name": "first_name", "type": "text", "label": "First Name", "required": true },
{ "name": "last_name", "type": "text", "label": "Last Name", "required": true },
{ "name": "bio", "type": "textarea", "label": "About You" },
{ "name": "newsletter", "type": "checkbox", "label": "Subscribe to newsletter" },
{ "name": "terms", "type": "checkbox", "label": "I agree to the terms", "required": true }
]
}
This creates a three-step registration form with per-step validation ensuring required fields are filled before the user can proceed.