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 28

<div class="flex min-h-dvh flex-col">
  <nav class="px-[5%]">
    <div class="flex min-h-16 items-center justify-between md:min-h-18">
      <a href="#"
        ><img src="https://d22po4pjz3o32e.cloudfront.net/logo-image.svg" alt="Logo image"
      /></a>
      <div class="flex items-center gap-x-1">
        <span class="hidden md:inline-block">Already have an account?</span
        ><button
          class="focus-visible:ring-border-primary inline-flex items-center justify-center whitespace-nowrap ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border-0 text-text-primary gap-2 p-0 underline"
          title="Log In"
        >
          Log In
        </button>
      </div>
    </div>
  </nav>
  <section id="relume" class="flex grow flex-col justify-center px-[5%] pb-5 pt-10">
    <div class="mx-auto w-full max-w-[40rem]">
      <form>
        <div class="text-center">
          <h2 class="text-2xl font-bold md:text-3xl md:leading-[1.3] lg:text-4xl">
            Product Survey
          </h2>
          <p class="mt-3 md:mt-4">
            We have a few questions for you to help us improve our product.
          </p>
        </div>
        <div class="mt-14 flex flex-col items-center gap-3 md:mt-16">
          <div class="relative">
            <button
              class="focus-visible:ring-border-primary inline-flex gap-3 items-center justify-center whitespace-nowrap ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-border-primary bg-background-alternative text-text-alternative px-6 py-3"
            >
              Get started
            </button>
            <div
              class="absolute right-0 top-1/2 hidden -translate-y-1/2 translate-x-full pl-4 lg:block"
            >
              <p class="hidden text-sm md:flex md:items-center">
                Press<span class="ml-1 mr-0.5 font-semibold">Enter</span
                ><svg
                  stroke="currentColor"
                  fill="currentColor"
                  stroke-width="0"
                  viewBox="0 0 24 24"
                  height="1em"
                  width="1em"
                  xmlns="http://www.w3.org/2000/svg"
                >
                  <path
                    fill="none"
                    stroke-width="2"
                    d="M9,4 L4,9 L9,14 M18,19 L18,9 L5,9"
                    transform="matrix(1 0 0 -1 0 23)"
                  ></path>
                </svg>
              </p>
            </div>
          </div>
          <p class="flex items-center gap-x-1.5 text-sm">
            <svg
              stroke="currentColor"
              fill="currentColor"
              stroke-width="0"
              viewBox="0 0 24 24"
              height="1em"
              width="1em"
              xmlns="http://www.w3.org/2000/svg"
            >
              <path fill="none" d="M0 0h24v24H0z"></path>
              <path
                d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"
              ></path>
              <path d="M12.5 7H11v6l5.25 3.15.75-1.23-4.5-2.67z"></path></svg
            >Takes 1 minute
          </p>
        </div>
      </form>
    </div>
  </section>
  <footer class="px-[5%]">
    <div class="flex min-h-16 items-center justify-center md:min-h-18">
      <p class="text-sm">© 2024 Relume</p>
    </div>
  </footer>
</div>
import {
  Button,
  Form,
  FormControl,
  FormField,
  FormItem,
  FormMessage,
  Input,
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@relume_io/relume-ui";
import type { ButtonProps } from "@relume_io/relume-ui";
import React, { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { BiCheck, BiEnvelope } from "react-icons/bi";
import { GrReturn } from "react-icons/gr";
import { MdAccessTime } from "react-icons/md";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import clsx from "clsx";

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

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

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

const formSchema = z.object({
  quality: z.string().min(1, { message: "This field is required" }),
  like: z.string().min(1, { message: "This field is required" }),
  areas: z.array(z.string()).nonempty({ message: "At least one option must be selected" }),
  about_us: 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" }),
});

type FormValues = z.infer<typeof formSchema>;

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

export const MultiForm28 = (props: MultiForm28Props) => {
  const { logo, navText, navButton, footerText } = {
    ...MultiForm28Defaults,
    ...props,
  };
  const [step, setStep] = useState(0);
  const form = useForm<FormValues>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      quality: "",
      like: "",
      areas: [],
      about_us: "",
      email: "",
    },
    mode: "onChange",
  });
  const onSubmit = (values: FormValues) => {
    console.log(values);
    // Handle form submission here
  };
  const steps = [
    { component: StepIntro, fields: null },
    { component: StepOne, fields: ["quality"] },
    { component: StepTwo, fields: ["like"] },
    { component: StepThree, fields: ["areas"] },
    { component: StepFour, fields: ["about_us"] },
    { component: StepFive, fields: ["email"] },
  ] 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 proceedToNextStep = () => setStep((prev) => prev + 1);

    if (isFirstStep) {
      proceedToNextStep();
    } else {
      const output = await form.trigger(currentFields ?? []);

      if (output) {
        if (isLastStep) {
          form.handleSubmit(onSubmit)();
        } else {
          proceedToNextStep();
        }
      }
    }
  };

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

  useEffect(() => {
    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === "Enter") {
        handleNext();
      }
    };

    document.addEventListener("keydown", handleKeyDown);
    return () => {
      document.removeEventListener("keydown", handleKeyDown);
    };
  }, [step, form, currentFields]);

  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)();
                }
              }}
            >
              {step > 0 && <ProgressBar step={step} totalSteps={totalSteps} />}
              <Step form={form} />
              <StepAction
                step={step}
                isFirstStep={isFirstStep}
                isLastStep={isLastStep}
                handleNext={handleNext}
                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 gap-6">
      {Array.from({ length: totalSteps - 1 }, (_, 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 + 1
                ? "bg-background-alternative text-text-alternative"
                : "bg-white text-text-primary",
            )}
          >
            {step > index + 1 ? <BiCheck className="size-6" /> : index + 1}
          </div>
          <p className="text-sm">Step {index + 1} title</p>
        </div>
      ))}
    </div>
  );
};

const StepAction = ({
  step,
  isFirstStep,
  isLastStep,
  handlePrev,
  handleNext,
}: {
  step: number;
  isFirstStep: boolean;
  isLastStep: boolean;
  handlePrev: () => void;
  handleNext: () => void;
}) =>
  step === 0 ? (
    <div className="mt-14 flex flex-col items-center gap-3 md:mt-16">
      <div className="relative">
        <Button onClick={handleNext}>Get started</Button>
        <div className="absolute right-0 top-1/2 hidden -translate-y-1/2 translate-x-full pl-4 lg:block">
          <p className="hidden text-sm md:flex md:items-center">
            Press
            <span className="ml-1 mr-0.5 font-semibold">Enter</span>
            <GrReturn />
          </p>
        </div>
      </div>
      <p className="flex items-center gap-x-1.5 text-sm">
        <MdAccessTime />
        Takes 1 minute
      </p>
    </div>
  ) : (
    <div className="mt-8 flex flex-wrap gap-x-4 gap-y-2">
      <Button onClick={handlePrev} type="button" variant="secondary" disabled={isFirstStep}>
        Back
      </Button>
      <Button type="submit">{isLastStep ? "Submit" : "Next"}</Button>
      <p className="hidden text-sm md:flex md:items-center">
        Press
        <span className="ml-1 mr-0.5 font-semibold">Enter</span>
        <GrReturn />
      </p>
    </div>
  );

const StepIntro = () => (
  <div className="text-center">
    <h2 className="text-2xl font-bold md:text-3xl md:leading-[1.3] lg:text-4xl">Product Survey</h2>
    <p className="mt-3 md:mt-4">We have a few questions for you to help us improve our product.</p>
  </div>
);

const StepOne: React.FC<StepProps> = ({ form }) => (
  <React.Fragment>
    <h2 className="mb-6 text-2xl font-bold leading-[1.3] md:text-4xl">
      Overall quality — how would you rate the product?
    </h2>
    <div className="inline-flex flex-col">
      <FormField
        control={form.control}
        name="quality"
        render={({ field }) => (
          <FormItem>
            <FormControl>
              <div className="flex flex-wrap gap-2">
                {[...Array(10)].map((_, index) => {
                  const value = (index + 1).toString();
                  return (
                    <Button
                      key={index}
                      className="size-[42px] p-0"
                      type="button"
                      variant={field.value === value ? "primary" : "secondary"}
                      onClick={() => field.onChange(value)}
                    >
                      {value}
                    </Button>
                  );
                })}
              </div>
            </FormControl>
            <FormMessage className="text-base text-text-error" />
          </FormItem>
        )}
      />
      <div className="mt-4 flex flex-col md:flex-row md:items-center md:justify-between">
        <p>0: Poor</p>
        <p>10: Excellent</p>
      </div>
    </div>
  </React.Fragment>
);

const StepTwo: React.FC<StepProps> = ({ form }) => (
  <React.Fragment>
    <h2 className="mb-6 text-2xl font-bold leading-[1.3] md:text-4xl">
      What did you like most about it?
    </h2>
    <FormField
      control={form.control}
      name="like"
      render={({ field, fieldState }) => (
        <FormItem>
          <FormControl>
            <Input className={clsx({ "border-border-error": fieldState.invalid })} {...field} />
          </FormControl>
          <FormMessage className="text-base text-text-error" />
        </FormItem>
      )}
    />
  </React.Fragment>
);

const StepThree: React.FC<StepProps> = ({ form }) => {
  const data = [
    {
      label: "Product features",
      value: "product-features",
    },
    {
      label: "Performance",
      value: "performance",
    },
    {
      label: "Accessibility",
      value: "accessibility",
    },
    {
      label: "Customer support",
      value: "customer-support",
    },
    {
      label: "Other",
      value: "other",
    },
  ];
  return (
    <React.Fragment>
      <h2 className="mb-6 text-2xl font-bold leading-[1.3] md:text-4xl">
        What areas do you think we should improve?
      </h2>
      <p className="mb-4">Choose as many as you like</p>
      <FormField
        control={form.control}
        name="areas"
        render={({ field }) => {
          return (
            <FormItem>
              <FormControl>
                <div className="flex flex-wrap gap-2">
                  {data.map((item, index) => {
                    const selectedValues = field.value || [];
                    const isSelected = selectedValues.indexOf(item.value) !== -1;
                    return (
                      <Button
                        key={index}
                        size="sm"
                        type="button"
                        variant={isSelected ? "primary" : "secondary"}
                        onClick={() => {
                          const updatedValues = isSelected
                            ? selectedValues.filter((val: string) => val !== item.value)
                            : [...selectedValues, item.value];
                          field.onChange(updatedValues);
                        }}
                        className="px-4"
                      >
                        {item.label}
                      </Button>
                    );
                  })}
                </div>
              </FormControl>
              <FormMessage className="text-base text-text-error" />
            </FormItem>
          );
        }}
      />
    </React.Fragment>
  );
};

const StepFour: React.FC<StepProps> = ({ form }) => {
  const selectItems = [
    { value: "first-choice", label: "First Choice" },
    { value: "second-choice", label: "Second Choice" },
    { value: "third-choice", label: "Third Choice" },
  ];
  return (
    <React.Fragment>
      <h2 className="mb-6 text-2xl font-bold leading-[1.3] md:text-4xl">
        How did you hear about us?
      </h2>
      <FormField
        control={form.control}
        name="about_us"
        render={({ field, fieldState }) => (
          <FormItem>
            <FormControl>
              <Select onValueChange={field.onChange} defaultValue={field.value}>
                <SelectTrigger className={clsx({ "border-border-error": fieldState.invalid })}>
                  <SelectValue placeholder="Select one..." />
                </SelectTrigger>
                <SelectContent>
                  {selectItems.map((item, index) => (
                    <SelectItem key={index} value={item.value}>
                      {item.label}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </FormControl>
            <FormMessage className="text-base text-text-error" />
          </FormItem>
        )}
      />
    </React.Fragment>
  );
};

const StepFive: React.FC<StepProps> = ({ form }) => (
  <React.Fragment>
    <h2 className="mb-6 text-2xl font-bold leading-[1.3] md:text-4xl">
      Finally, add your email to get an exclusive discount
    </h2>
    <FormField
      control={form.control}
      name="email"
      render={({ field, fieldState }) => (
        <FormItem>
          <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>
      )}
    />
  </React.Fragment>
);

export const MultiForm28Defaults: 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.