About this component

The Form component is a super versatile tool that can fit right into your product UI or pop up in a modal for things like surveys. They can be used for wide range of use cases including registration flows, surveys, feedback forms, and more. The component supports form validation (client and server-side), conditional fields, branching logic, and multi-step Flows.

When to Use Forms:

  • Embedded in UI: Forms work great for tasks like product registration, helping users get started smoothly without leaving the page.
  • Modal Surveys: Use forms in modals for surveys or feedback, making it easy for users to share their thoughts without disrupting their experience.

Why Forms Are Powerful:

  • Conditional and Branching Logic: Forms can adapt based on user responses, guiding them through a tailored experience that feels intuitive.
  • Custom React Steps: You can embed custom React components to invite teammates or perform API lookups, adding a personal touch to your forms.
  • Customizable Input Types: With a variety of built-in input types—like text fields, multiple-choice options, and dropdowns—you can design forms that suit your specific needs.

Best Practices for Forms:

  • Provide Progress Indicators: Adding progress bars or step indicators (like “Step X of Y”) can help users see how far they’ve come and what’s left to do. This makes the process feel less daunting and more manageable.
  • Streamlined Data Collection: Frigade makes it easy to create new forms quickly, allowing you to gather user data and send it wherever you need it in your system.

Resources

Demo

Customization

To learn about how to customize Frigade components, see the customization documentation and examples of custom themes in action.

Examples

The following section includes ready-made examples and code for various form use cases.

Simple Modal Form

Churn Survey

Dynamic Fields

Sometimes you may want to conditionally render a dynamic field based on the value of another field. The example above dynamically changes the second dropdown based on the value of the first dropdown.

Branching Forms

This example shows how to implement branching in a form based on the user’s choice in the first step.

Supported Field Types

The component supports the following builtin field types that correspond to their respective HTML input types:

  • select

  • radio

  • text

  • textarea

  • checkbox

Overriding Field Attributes

You can override or add any attribute for a field by using the props property in the field configuration. For instance, this is useful if you want to use the text field type, but override the type to email or tel. It can also be used to add any attribute such as a css class, data, or styling.

steps:
  - id: step-1
    title: This is page 1
    fields:
      - id: email
        type: text
        props:
          type: email
          className: "my-custom-class"
          data-attr: "my-custom-data-attr"
          style:
            color: "red"

Custom Field Types

The Form SDK is built on top of react-hook-form, which means you can use the majority of its features in your forms. You can define your own custom field types using the fieldTypes prop. For instance, you can implement a simple calendar datepicker field type as such:

import { FormFieldProps } from "@frigade/react";
import * as Frigade from "@frigade/react";

function CalendarField({ field, submit }: FormFieldProps) {
  return (
    <div>
      <input type="date" onChange={field.onChange} value={field.value} />
    </div>
  );
}

// ...

<Frigade.Form flowId="my-flow-id" fieldTypes={{ calendar: CalendarField }} />;

It is also possible to conditionally render a field based on the value of another field by using the formContext provided by react-hook-form. For instance, if you want a custom field called company-size to show up when a user selects company in the customer-type field:

import { type FormFieldProps, SelectField } from "@frigade/react";
import * as Frigade from "@frigade/react";

<Frigade.Form
  flowId="myflowid"
  fieldTypes={{
    "company-size": (props: FormFieldProps) => {
      const customerTypeValue = props.formContext.watch("customer-type");

      if (customerTypeValue !== "company") {
        return null;
      }

      return <div>My custom conditional field</div>;
    },
  }}
/>;

Form Validation

The component supports client-side and server-side validation out of the box. You can define validation rules for each field in the form configuration using the pattern property with a regular expression. The example below shows how to validate an email field:

steps:
  - id: collect-intend
    fields:
      - id: email
        type: text
        required: true
        pattern:
          message: Please provide a valid email
          value: ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$

Server-side Validation

You can perform server-side validation by returning a Promise from the onPrimary event handler. If the promise resolves to false, the current step in the form will not be marked as completed. The onPrimary event handler also contains all form data collected in the session, which allows you to send the data to your server for validation or storage.

import { StepHandlerProp } from "@frigade/react";
import * as Frigade from "@frigade/react";

const App = () => {
  const handlePrimary: StepHandlerProp = async (step, event, properties) => {
    const response = await fetch("https://my-server.com/validate", {
      method: "POST",
      body: JSON.stringify(properties),
      headers: {
        "Content-Type": "application/json",
      },
    });

    if (response.ok) {
      return true;
    }

    // The current step in the form will be marked as completed
    return false;
  };

  return <Frigade.Form flowId="my-flow-id" onPrimary={handlePrimary} />;
};

Prefilling a form

Forms can be prefilled by using Dynamic Variables by linking the value of a field to the variables prop of the Form component. The example below shows how to prefill a form with the user’s name:

import * as Frigade from '@frigade/react';

const App = () => {
return (

<Frigade.Form
flowId="my-flow-id"
variables={{
  name: "John Doe",
}}
/>
) }

Customization

To learn about how to customize Frigade components, see the customization documentation and examples of custom themes in action.

SDK Properties

as
ElementType<any, keyof IntrinsicElements>

Optional component to wrap the child components in, e.g. as={Dialog} will render the Flow in a modal Dialog. Defaults to Box.

autoStart
boolean

Whether to automatically mark the Flow started (i.e. in progress) when the Flow is eligible to be shown. You will need to call flow.start() or step.start() from the parent component if you set this to false. Most components should not need to override this behavior.

Defaults to true.

css
Interpolation<Theme>

Emotion CSS prop to apply to the component. See Theming documentation for more information.

Example usage:

<Frigade.Checklist css={{ backgroundColor: "pink", ".fr-button-primary": { backgroundColor: "fuchsia" } }} />
dismissible
boolean

Whether the Flow is dismissible or not

fieldTypes
FieldTypes

Custom field types to be used in the Form. You can use this to build your own custom form fields in a Form.

For example, if you want to use a custom field type called calendar:

import { Form, FormFieldProps } from '@frigade/react'

function CalendarField({ field, submit }: FormFieldProps) {
  return (
   <div>
     <input type="date" onChange={field.onChange} value={field.value} />
   </div>
  )
}

 // ...

 <Form flowId="my-flow-id" fieldTypes={{ calendar: CalendarField }} />

flowId
string

The Flow ID to render. You can find the Flow ID in the Frigade dashboard.

forceMount
boolean

If true, the Flow will be mounted even if it has already been completed or dismissed. However, if the user does not match the Flow’s targeting, the Flow will not be mounted.

modal
boolean

Register the Flow as a modal to prevent popup collisions (only one modal Flow will render at a time).

onComplete
FlowHandlerProp

Handler for when the Flow is completed. This is event is fired immediately after the user completes the Flow.

onDismiss
FlowHandlerProp

Handler for when the Flow is dismissed (skipped). This is event is fired immediately after the user dismisses the Flow.

onPrimary
StepHandlerProp

Handler for when primary button is clicked. If this function returns false or a promise that resolves to false, the step will not be automatically completed when clicked.

onSecondary
StepHandlerProp

Handler for when secondary button is clicked. If this function returns false or a promise that resolves to false, the step will not be automatically completed when clicked.

variables
Record<string, unknown>

Variables to pass to the Flow. You can use variables in the Flow configuration to customize copy. For instance, you can use title: Hello, ${name}! in the Flow configuration and pass variables={{name: 'John'}} to customize the copy.