7/24 | Livestream: Training AI on your design system

Announcing Visual Copilot - Figma to production in half the time

Builder logo
builder.io
Contact sales

7/24 | Livestream: Training AI on your design system

Announcing Visual Copilot - Figma to production in half the time

Builder logo
builder.io

Blog

Home

Resources

Blog

Forum

Github

Login

Signup

×

Visual CMS

Drag-and-drop visual editor and headless CMS for any tech stack

Theme Studio for Shopify

Build and optimize your Shopify-hosted storefront, no coding required

Resources

Blog

Get StartedLogin

Create a multi-step contact form that collects user information across separate screens with configurable default values through Builder.

Multi-step forms improve user experience by breaking form fields into focused sections.

This tutorial creates a contact form with three steps: basic information, message details, and review. Users with sufficient permissions can customize the component using the Visual Editor.

Before you begin, you need:

This tutorial results in a registered with the Builder registry that collects user information through a multi-step form and provides the flexibility of configuring default values through the Builder's Visual Editor:

Create a React component that manages form steps and integrates with Builder props.

Create the component file src/components/ContactForm.tsx to handle user input, manages navigation between steps, and displays a review screen before submission:

// src/components/ContactForm.tsx

import React, { useState } from 'react';
import './styles/ContactForm.css';

interface ContactFormProps {
  defaultName?: string;
  defaultEmail?: string;
  defaultMessage?: string;
}

export function ContactForm({
  defaultName = '',
  defaultEmail = '',
  defaultMessage = ''
}: ContactFormProps) {
  const [currentStep, setCurrentStep] = useState(0);
  const [formData, setFormData] = useState({
    name: defaultName,
    email: defaultEmail,
    message: defaultMessage
  });
  const [isSubmitted, setIsSubmitted] = useState(false);

  const handleInputChange = (field: string, value: string) => {
    setFormData(prev => ({ ...prev, [field]: value }));
  };

  const handleNext = () => setCurrentStep(currentStep + 1);
  const handlePrevious = () => setCurrentStep(currentStep - 1);
  const handleSubmit = () => setIsSubmitted(true);

  if (isSubmitted) {
    return (
      <div className="contact-form-success">
        <h3>Thank you!</h3>
        <p>Your message has been sent.</p>
      </div>
    );
  }

  return (
    <div className="contact-form">
      <h2>Contact Us</h2>

      {/* Step 1: Basic Information */}
      {currentStep === 0 && (
        <div className="form-step">
          <div className="form-field">
            <label htmlFor="name">Name</label>
            <input
              id="name"
              type="text"
              value={formData.name}
              onChange={(e) => handleInputChange('name', e.target.value)}
              placeholder="Enter your name"
            />
          </div>

          <div className="form-field">
            <label htmlFor="email">Email</label>
            <input
              id="email"
              type="email"
              value={formData.email}
              onChange={(e) => handleInputChange('email', e.target.value)}
              placeholder="Enter your email"
            />
          </div>

          <button onClick={handleNext} className="btn-primary">
            Next
          </button>
        </div>
      )}

      {/* Step 2: Message */}
      {currentStep === 1 && (
        <div className="form-step">
          <div className="form-field">
            <label htmlFor="message">Message</label>
            <textarea
              id="message"
              value={formData.message}
              onChange={(e) => handleInputChange('message', e.target.value)}
              placeholder="Enter your message"
              rows={4}
            />
          </div>

          <div className="form-buttons">
            <button onClick={handlePrevious} className="btn-secondary">
              Previous
            </button>
            <button onClick={handleNext} className="btn-primary">
              Review
            </button>
          </div>
        </div>
      )}

      {/* Step 3: Review */}
      {currentStep === 2 && (
        <div className="form-step">
          <h3>Review your information</h3>

          <div className="review-section">
            <div className="review-item">
              <strong>Name:</strong>
              <span>{formData.name || 'Not provided'}</span>
            </div>

            <div className="review-item">
              <strong>Email:</strong>
              <span>{formData.email || 'Not provided'}</span>
            </div>

            <div className="review-item">
              <strong>Message:</strong>
              <span>{formData.message || 'Not provided'}</span>
            </div>
          </div>

          <div className="form-buttons">
            <button onClick={handlePrevious} className="btn-secondary">
              Previous
            </button>
            <button onClick={handleSubmit} className="btn-primary">
              Send Message
            </button>
          </div>
        </div>
      )}

      <div className="step-indicator">
        Step {currentStep + 1} of 3
      </div>
    </div>
  );
}

Create the CSS file src/styles/ContactForm.css to provide a clean, professional form layout with proper spacing, hover effects, and responsive design:

/* /styles/ContactForm.css */

.contact-form {
  max-width: 500px;
  margin: 0 auto;
  padding: 24px;
  border: 1px solid #e1e5e9;
  border-radius: 8px;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
}

.contact-form h2 {
  margin-bottom: 24px;
  text-align: center;
  color: #1f2937;
}

.form-step {
  min-height: 200px;
}

.form-field {
  margin-bottom: 16px;
}

.form-field label {
  display: block;
  margin-bottom: 8px;
  font-weight: 500;
  color: #374151;
}

.form-field input,
.form-field textarea {
  width: 100%;
  padding: 12px;
  border: 1px solid #d1d5db;
  border-radius: 4px;
  font-size: 16px;
  box-sizing: border-box;
}

.form-field input:focus,
.form-field textarea:focus {
  outline: none;
  border-color: #2563eb;
  box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.2);
}

.form-buttons {
  display: flex;
  gap: 12px;
}

.btn-primary,
.btn-secondary {
  padding: 12px 24px;
  border: none;
  border-radius: 4px;
  font-weight: 500;
  cursor: pointer;
  font-size: 16px;
}

.btn-primary {
  background-color: #2563eb;
  color: white;
}

.btn-primary:hover {
  background-color: #1d4ed8;
}

.btn-secondary {
  background-color: #f3f4f6;
  color: #374151;
}

.btn-secondary:hover {
  background-color: #e5e7eb;
}

.step-indicator {
  text-align: center;
  margin-top: 16px;
  font-size: 14px;
  color: #6b7280;
}

.contact-form-success {
  text-align: center;
  padding: 32px;
  max-width: 500px;
  margin: 0 auto;
  border: 1px solid #e1e5e9;
  border-radius: 8px;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
}

.contact-form-success h3 {
  color: #059669;
  margin-bottom: 8px;
}

.contact-form-success p {
  color: #374151;
}

.review-section {
  background-color: #f9fafb;
  border: 1px solid #e5e7eb;
  border-radius: 6px;
  padding: 16px;
  margin-bottom: 20px;
}

.review-item {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 12px;
  padding-bottom: 8px;
  border-bottom: 1px solid #e5e7eb;
}

.review-item:last-child {
  margin-bottom: 0;
  border-bottom: none;
}

.review-item strong {
  color: #374151;
  font-weight: 600;
  min-width: 80px;
  margin-right: 16px;
}

.review-item span {
  color: #6b7280;
  text-align: right;
  word-break: break-word;
  flex: 1;
}

This tutorial uses standard CSS to keep things clear and straightforward. You can use Tailwind CSS, CSS modules, or any styling method you prefer.

Create the registration file src/components/ContactFormInfo.ts with the following configuration to make your component available in the Visual Editor.

// src/components/ContactFormInfo.ts

import { RegisteredComponent } from '@builder.io/sdk-react';
import { ContactForm } from './ContactForm';

export const contactFormInfo: RegisteredComponent = {
  component: ContactForm,
  name: 'ContactForm',
  inputs: [
    {
      name: 'defaultName',
      type: 'string',
      friendlyName: 'Default Name',
      defaultValue: 'John Doe'
    },
    {
      name: 'defaultEmail',
      type: 'string',
      friendlyName: 'Default Email',
      defaultValue: 'john@example.com'
    },
    {
      name: 'defaultMessage',
      type: 'longText',
      friendlyName: 'Default Message',
      defaultValue: 'Hello, I would like to get in touch...'
    }
  ]
};

The contactFormInfo object defines how Builder handles your component. The component property references your React component, while name sets the display name in Builder's component library. The inputs array defines configuration options that appear in the Options tab.

Each input configuration creates a field where users can customize the component. The name property becomes a prop passed to your React component, while friendlyName provides a human-readable label in the Options tab. The type property defines the input type, and defaultValue sets the initial value when the component is added to Builder.

Register your component with a Builder page to make it available in the Visual Editor.

Create the page file src/pages/FormPage.tsx:

// src/pages/FormPage.tsx

import React from 'react';
import { Content } from '@builder.io/sdk-react';
import { contactFormInfo } from '../components/ContactFormInfo';

// Replace this with your actual API key from Builder
const BUILDER_API_KEY = 'YOUR_API_KEY_HERE';

export default function FormPage() {
  return (
      <Content
        model="page"
        apiKey={BUILDER_API_KEY}
        customComponents={[contactFormInfo]}
      />
  );
}

The Builder component connects your React app to Builder's Visual Editor. When Builder loads content, it replaces this component with the visual content created in Builder's editor, including any custom components you've registered through the customComponents array.

Update your main component to include the contact page by modifying src/App.tsx:

// src/App.tsx

import React from 'react';
import FormPage from './pages/FormPage';
import './App.css';

function App() {
  return (
    <div className="App">
      <FormPage />
    </div>
  );
}

export default App;

Before connecting to Builder, verify your component works in your local development environment.

  1. Make sure your development server is running with npm run dev.
  2. Open your browser to http://localhost:5173.
  3. Test the form by filling out fields, navigating between steps, and submitting.

Your form should work correctly in your local environment before connecting to Builder.

To see the multi-step form in action:

  1. Replace the placeholder API key in src/pages/FormPage.tsx with your Public API key.
  2. Open Content entry to access the Visual Editor.
  3. Drag your ContactForm component from the Custom Components section to your page.
  4. Use the Options tab to configure default values when selecting the component.
  5. Click Publish to make your changes live with the URL defined.

Once your basic form is working, you can enhance the component configuration options to give content creators more control over appearance and behavior. Builder supports many input types beyond basic strings to create rich configuration interfaces.

Use enum types to provide predefined options that ensure consistency across your site:

{
  name: 'formTheme',
  type: 'string',
  enum: [
    { label: 'Light', value: 'light' },
    { label: 'Dark', value: 'dark' }
  ],
  friendlyName: 'Form Theme',
  defaultValue: 'light'
}

This creates a dropdown menu in the Options tab where users can select between light and dark themes. The selected value gets passed to your component as the formTheme prop, allowing you to conditionally apply CSS classes or styling based on the selection.

Boolean inputs provide on/off switches for optional features:

{
  name: 'showProgressBar',
  type: 'boolean',
  defaultValue: true,
  friendlyName: 'Show Progress Indicator'
}

This creates a checkbox that content creators can toggle to show or hide the progress indicator. Your component can then conditionally render the step indicator based on this prop.

Color inputs provide visual color pickers for brand consistency:

{
  name: 'buttonColor',
  type: 'color',
  defaultValue: '#2563eb',
  friendlyName: 'Button Color'
}

This creates a color picker that lets users customize the button color to match their brand. The selected color value gets passed to your component, where you can apply it as an inline style or CSS variable.

Enhance the configuration experience with additional options:

{
  name: 'defaultName',
  type: 'string',
  friendlyName: 'Default Name',
  defaultValue: 'John Doe',
  helperText: 'The name that appears when the form loads',
  required: true
}

The helperText property provides descriptive text below the input to clarify its purpose, while required marks the field as mandatory.

These examples demonstrate just a few of the available customization options. Builder provides many other input types for custom components, including file uploads, rich text editors, and reference fields.

You can also create dynamic configuration interfaces using ShowIf with registered components to show or hide inputs based on other field values.

You now have a working multi-step form that content creators can customize through Builder. Consider these next steps:

Was this article helpful?

Product

Visual CMS

Theme Studio for Shopify

Sign up

Login

Featured Integrations

React

Angular

Next.js

Gatsby

Get In Touch

Chat With Us

Twitter

Linkedin

Careers

© 2020 Builder.io, Inc.

Security

Privacy Policy

Terms of Service

Get the latest from Builder.io

By submitting, you agree to our Privacy Policy

  • Fusion

  • Publish

  • Product Updates

  • Figma to Code Guide

  • Headless CMS Guide

  • Headless Commerce Guide

  • Composable DXP Guide

© 2025 Builder.io, Inc.

Security

Privacy Policy

SaaS Terms

Compliance

Cookie Preferences

Gartner Cool Vendor 2024