Support
All support for the Relume is provided through Slack. To get assistance, please join our Slack community and send a preview link of your Webflow project along with a description of your problem to one of our experts. We will review your issue and guide you through a solution.

For account-related issues, please contact support@relume.io.
We're performing some maintenance. If you're experiencing any issues please reach out via Slack
Go to Slack
Your payment method has expired. Update your billing details to regain access to premium features.
Manage Billing
Now open!
Vote on new
Relume components
Vote on what components you'd like to see added to our roadmap next month.
Get started

Multi Form 5

import {
  Button,
  Calendar,
  cn,
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
  Input,
  Popover,
  PopoverContent,
  PopoverTrigger,
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@relume_io/relume-ui";
import type { ButtonProps } from "@relume_io/relume-ui";
import React, { useState } from "react";
import { useForm } from "react-hook-form";
import { BiCheck, BiEnvelope } from "react-icons/bi";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import clsx from "clsx";
import { FaRegCalendar } from "react-icons/fa6";
import { DateTime } from "luxon";

type ImageProps = {
  url?: string;
  src: string;
  alt?: string;
};

type Props = {
  logo: ImageProps;
  navText: string;
  navButton: ButtonProps;
  footerText: string;
};

export type MultiForm5Props = React.ComponentPropsWithoutRef<"section"> & Partial<Props>;

const formSchema = z.object({
  name: z.string().min(1, {
    message: "This field is required",
  }),
  email: z
    .string()
    .min(1, { message: "This field is required" })
    .email({ message: "Please enter a valid email address" }),
  serviceType: z.string().min(1, { message: "This field is required" }),
  budget: z.string().min(1, { message: "This field is required" }),
  aboutProject: z.string().min(1, { message: "This field is required" }),
  companyName: z.string().min(1, { message: "This field is required" }),
  employees: z.string().min(1, { message: "This field is required" }),
  website: z.string().min(1, { message: "This field is required" }).url(),
  country: z.string().min(1, { message: "This field is required" }),
  date: z.date({
    required_error: "This field is required",
  }),
});

type FormValues = z.infer<typeof formSchema>;

interface StepProps {
  form: ReturnType<typeof useForm<FormValues>>;
}

export const MultiForm5 = (props: MultiForm5Props) => {
  const { logo, navText, navButton, footerText } = {
    ...MultiForm5Defaults,
    ...props,
  };
  const [step, setStep] = useState(0);
  const form = useForm<FormValues>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      name: "",
      email: "",
      serviceType: "",
      budget: "",
      aboutProject: "",
      companyName: "",
      employees: "",
      website: "",
      country: "",
    },
    mode: "onChange",
  });
  const onSubmit = (values: FormValues) => {
    console.log(values);
    // Handle form submission here
  };
  const steps = [
    { component: StepOne, fields: ["name", "email"] },
    { component: StepTwo, fields: ["serviceType", "budget", "aboutProject"] },
    { component: StepThree, fields: ["companyName", "employees", "website"] },
    { component: StepFour, fields: ["country", "date"] },
  ] as const;
  const Step = steps[step].component;
  const currentFields = steps[step].fields;
  const totalSteps = steps.length;
  const isLastStep = step === steps.length - 1;
  const isFirstStep = step === 0;
  const handleNext = async () => {
    const output = await form.trigger(currentFields);
    if (output) {
      setStep((prev) => prev + 1);
    }
  };

  const handlePrev = () => {
    if (!isFirstStep) {
      setStep((prev) => prev - 1);
    }
  };

  return (
    <div className="flex min-h-dvh flex-col">
      <nav className="px-[5%]">
        <div className="flex min-h-16 items-center justify-between md:min-h-18">
          <a href={logo.url}>
            <img src={logo.src} alt={logo.alt} />
          </a>
          <div className="flex items-center gap-x-1">
            <span className="hidden md:inline-block">{navText}</span>
            <Button className="underline" {...navButton}>
              {navButton.title}
            </Button>
          </div>
        </div>
      </nav>
      <section id="relume" className="flex grow flex-col justify-center px-[5%] pb-5 pt-10">
        <div className="mx-auto w-full max-w-[40rem]">
          <Form {...form}>
            <form
              onSubmit={(e) => {
                e.preventDefault();
                if (!isLastStep) {
                  handleNext();
                } else {
                  form.handleSubmit(onSubmit)();
                }
              }}
            >
              <ProgressBar step={step} totalSteps={totalSteps} />
              <Step form={form} />
              <StepAction
                isFirstStep={isFirstStep}
                isLastStep={isLastStep}
                handlePrev={handlePrev}
              />
            </form>
          </Form>
        </div>
      </section>
      <footer className="px-[5%]">
        <div className="flex min-h-16 items-center justify-center md:min-h-18">
          <p className="text-sm">{footerText}</p>
        </div>
      </footer>
    </div>
  );
};

const ProgressBar = ({ step, totalSteps }: { step: number; totalSteps: number }) => {
  return (
    <div className="mb-8 flex flex-wrap items-start justify-center gap-x-6 gap-y-4">
      {Array.from({ length: totalSteps }, (_, index) => (
        <div className="flex items-center gap-x-2" key={index}>
          <div
            className={clsx(
              "flex size-6 items-center justify-center rounded-full border border-border-primary text-sm",
              step >= index
                ? "bg-background-alternative text-text-alternative"
                : "bg-white text-text-primary",
            )}
          >
            {step > index ? <BiCheck className="size-6" /> : index + 1}
          </div>
          <p className="text-sm">Step title</p>
        </div>
      ))}
    </div>
  );
};

const StepAction = ({
  isFirstStep,
  isLastStep,
  handlePrev,
}: {
  isFirstStep: boolean;
  isLastStep: boolean;
  handlePrev: () => void;
}) => (
  <div className="mt-6 flex flex-wrap justify-end gap-4">
    <Button
      type="button"
      variant="secondary"
      onClick={() => {
        if (!isFirstStep) {
          handlePrev();
        }
      }}
    >
      {isFirstStep ? "Cancel" : "Back"}
    </Button>
    <Button type="submit">{isLastStep ? "Get started" : "Next"}</Button>
  </div>
);

const StepOne: React.FC<StepProps> = ({ form }) => (
  <React.Fragment>
    <div className="mb-6 text-center md:mb-8">
      <h2 className="text-2xl font-bold md:text-3xl md:leading-[1.3] lg:text-4xl">
        Let’s start with your name & email
      </h2>
      <p className="mt-3 md:mt-4">
        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.
      </p>
    </div>
    <div className="flex flex-col gap-y-6">
      <FormField
        control={form.control}
        name="name"
        render={({ field, fieldState }) => (
          <FormItem>
            <FormLabel className={clsx({ "text-text-error": fieldState.invalid })}>
              Enter your name
            </FormLabel>
            <FormControl>
              <Input className={clsx({ "border-border-error": fieldState.invalid })} {...field} />
            </FormControl>
            <FormMessage className="text-base text-text-error" />
          </FormItem>
        )}
      />
      <FormField
        control={form.control}
        name="email"
        render={({ field, fieldState }) => (
          <FormItem>
            <FormLabel className={clsx({ "text-text-error": fieldState.invalid })}>
              Enter your email*
            </FormLabel>
            <FormControl>
              <Input
                placeholder="hello@relume.io"
                iconPosition="left"
                icon={<BiEnvelope className="size-6" />}
                className={clsx({ "border-border-error": fieldState.invalid })}
                {...field}
              />
            </FormControl>
            <FormMessage className="text-base text-text-error" />
          </FormItem>
        )}
      />
    </div>
  </React.Fragment>
);

const StepTwo: React.FC<StepProps> = ({ form }) => {
  const serviceTypeData = [
    {
      label: "Website design",
      value: "A",
    },
    {
      label: "Webflow development",
      value: "B",
    },
    {
      label: "Custom code solutions",
      value: "C",
    },
    {
      label: "Other",
      value: "D",
    },
  ];
  return (
    <React.Fragment>
      <div className="mb-6 text-center md:mb-8">
        <h2 className="text-2xl font-bold md:text-3xl md:leading-[1.3] lg:text-4xl">
          What can we help you with?
        </h2>
        <p className="mt-3 md:mt-4">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.
        </p>
      </div>
      <div className="flex flex-col gap-y-6">
        <FormField
          control={form.control}
          name="serviceType"
          render={({ field, fieldState }) => (
            <FormItem>
              <FormLabel className={clsx({ "text-text-error": fieldState.invalid })}>
                Service type
              </FormLabel>
              <FormControl>
                <div className="flex flex-wrap gap-2">
                  {serviceTypeData.map((item, index) => {
                    return (
                      <Button
                        key={index}
                        className="py-2 pl-2 pr-4"
                        type="button"
                        variant={field.value === item.value ? "primary" : "secondary"}
                        onClick={() => field.onChange(item.value)}
                      >
                        <span className="mr-2 inline-flex size-8 items-center justify-center border border-border-primary uppercase">
                          {item.value}
                        </span>
                        {item.label}
                      </Button>
                    );
                  })}
                </div>
              </FormControl>
              <FormMessage className="text-base text-text-error" />
            </FormItem>
          )}
        />
        <FormField
          control={form.control}
          name="budget"
          render={({ field, fieldState }) => (
            <FormItem>
              <FormLabel className={clsx({ "text-text-error": fieldState.invalid })}>
                Your budget
              </FormLabel>
              <FormControl>
                <Select onValueChange={field.onChange} defaultValue={field.value}>
                  <SelectTrigger className={clsx({ "border-border-error": fieldState.invalid })}>
                    <SelectValue placeholder="Select one..." />
                  </SelectTrigger>
                  <SelectContent>
                    <SelectItem value="first">First choice</SelectItem>
                    <SelectItem value="second">Second choice</SelectItem>
                    <SelectItem value="third">Third choice</SelectItem>
                  </SelectContent>
                </Select>
              </FormControl>
              <FormMessage className="text-base text-text-error" />
            </FormItem>
          )}
        />
        <FormField
          control={form.control}
          name="aboutProject"
          render={({ field, fieldState }) => (
            <FormItem>
              <FormLabel className={clsx({ "text-text-error": fieldState.invalid })}>
                About the project
              </FormLabel>
              <FormControl>
                <Input className={clsx({ "border-border-error": fieldState.invalid })} {...field} />
              </FormControl>
              <FormMessage className="text-base text-text-error" />
            </FormItem>
          )}
        />
      </div>
    </React.Fragment>
  );
};

const StepThree: React.FC<StepProps> = ({ form }) => {
  const employeesData = [
    {
      label: "Just me",
      value: "1",
    },
    {
      label: "2-10",
      value: "2-10",
    },
    {
      label: "11-50",
      value: "11-50",
    },
    {
      label: "51-100",
      value: "51-100",
    },
    {
      label: "101-500",
      value: "101-500",
    },
    {
      label: "501+",
      value: "501+",
    },
  ];
  return (
    <React.Fragment>
      <div className="mb-6 text-center md:mb-8">
        <h2 className="text-2xl font-bold md:text-3xl md:leading-[1.3] lg:text-4xl">
          Let's confirm your company info
        </h2>
        <p className="mt-3 md:mt-4">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.
        </p>
      </div>
      <div className="flex flex-col gap-y-6">
        <FormField
          control={form.control}
          name="companyName"
          render={({ field, fieldState }) => (
            <FormItem>
              <FormLabel className={clsx({ "text-text-error": fieldState.invalid })}>
                What is your company name?
              </FormLabel>
              <FormControl>
                <Input className={clsx({ "border-border-error": fieldState.invalid })} {...field} />
              </FormControl>
              <FormMessage className="text-base text-text-error" />
            </FormItem>
          )}
        />
        <FormField
          control={form.control}
          name="employees"
          render={({ field, fieldState }) => (
            <FormItem>
              <FormLabel className={clsx({ "text-text-error": fieldState.invalid })}>
                How many people are you working with?
              </FormLabel>
              <FormControl>
                <div className="flex flex-wrap gap-2">
                  {employeesData.map((item, index) => {
                    return (
                      <Button
                        key={index}
                        type="button"
                        className="px-4 py-2"
                        variant={field.value === item.value ? "primary" : "secondary"}
                        onClick={() => field.onChange(item.value)}
                      >
                        {item.label}
                      </Button>
                    );
                  })}
                </div>
              </FormControl>
              <FormMessage className="text-base text-text-error" />
            </FormItem>
          )}
        />
        <FormField
          control={form.control}
          name="website"
          render={({ field, fieldState }) => (
            <FormItem>
              <FormLabel className={clsx({ "text-text-error": fieldState.invalid })}>
                Website link
              </FormLabel>
              <FormControl>
                <Input className={clsx({ "border-border-error": fieldState.invalid })} {...field} />
              </FormControl>
              <FormMessage className="text-base text-text-error" />
            </FormItem>
          )}
        />
      </div>
    </React.Fragment>
  );
};

const StepFour: React.FC<StepProps> = ({ form }) => {
  const countriesData = [
    { id: 1, value: "US", label: "United States" },
    { id: 2, value: "CA", label: "Canada" },
    { id: 3, value: "GB", label: "United Kingdom" },
    { id: 4, value: "AU", label: "Australia" },
    { id: 5, value: "DE", label: "Germany" },
    { id: 6, value: "FR", label: "France" },
    { id: 7, value: "IT", label: "Italy" },
    { id: 8, value: "ES", label: "Spain" },
    { id: 9, value: "JP", label: "Japan" },
    { id: 10, value: "CN", label: "China" },
    { id: 11, value: "IN", label: "India" },
    { id: 12, value: "BR", label: "Brazil" },
    { id: 13, value: "ZA", label: "South Africa" },
    { id: 14, value: "RU", label: "Russia" },
    { id: 15, value: "MX", label: "Mexico" },
  ];
  return (
    <React.Fragment>
      <div className="mb-6 text-center md:mb-8">
        <h2 className="text-2xl font-bold md:text-3xl md:leading-[1.3] lg:text-4xl">
          Let's confirm your company info
        </h2>
        <p className="mt-3 md:mt-4">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.
        </p>
      </div>
      <div className="flex flex-col gap-y-6">
        <FormField
          control={form.control}
          name="country"
          render={({ field, fieldState }) => (
            <FormItem>
              <FormLabel className={clsx({ "text-text-error": fieldState.invalid })}>
                Country
              </FormLabel>
              <FormControl>
                <Select onValueChange={field.onChange} defaultValue={field.value}>
                  <SelectTrigger className={clsx({ "border-border-error": fieldState.invalid })}>
                    <SelectValue placeholder="Select one..." />
                  </SelectTrigger>
                  <SelectContent>
                    {countriesData.map((country) => (
                      <SelectItem key={country.id} value={country.value}>
                        {country.label}
                      </SelectItem>
                    ))}
                  </SelectContent>
                </Select>
              </FormControl>
              <FormMessage className="text-base text-text-error" />
            </FormItem>
          )}
        />
        <FormField
          control={form.control}
          name="date"
          render={({ field, fieldState }) => (
            <FormItem className="flex flex-col">
              <FormLabel className={clsx({ "text-text-error": fieldState.invalid })}>
                Preferred date for chat
              </FormLabel>
              <Popover>
                <PopoverTrigger asChild>
                  <Button
                    variant={"secondary"}
                    className={cn(
                      "justify-start py-[9px] pl-3 text-left font-normal",
                      !field.value && "text-muted-foreground",
                      fieldState.invalid && "border-border-error",
                    )}
                    type="button"
                  >
                    <FaRegCalendar className="size-5" />
                    {field.value ? (
                      DateTime.fromJSDate(field.value).toFormat("dd/MM/yyyy")
                    ) : (
                      <span>dd/mm/yyyy</span>
                    )}
                  </Button>
                </PopoverTrigger>
                <PopoverContent className="w-auto p-0" align="start">
                  <Calendar
                    mode="single"
                    selected={field.value}
                    onSelect={(date) => field.onChange(date)}
                    disabled={(date) =>
                      DateTime.fromJSDate(date).startOf("day") < DateTime.now().startOf("day")
                    }
                    initialFocus
                  />
                </PopoverContent>
              </Popover>
              <FormMessage className="text-base text-text-error" />
            </FormItem>
          )}
        />
      </div>
    </React.Fragment>
  );
};

export const MultiForm5Defaults: Props = {
  logo: {
    url: "#",
    src: "https://d22po4pjz3o32e.cloudfront.net/logo-image.svg",
    alt: "Logo image",
  },
  navText: "Already have an account?",
  navButton: {
    title: "Log In",
    variant: "link",
    size: "link",
  },
  footerText: "© 2024 Relume",
};
You need to be logged in to view the code.
Get the code
Upgrade your plan to view the code.
Upgrade
Details
Last updated
May 29, 2025
React version
18
Tailwind version
3.4
Need help?
For installation guidelines and API information, visit the docs.
Examples
No items found.