Meet Frontfriend.Your brand. Your rules. Your agents, connected.Explore what’s new

Form

Building forms with React Hook Form and Zod.

Interactive preview

Rendered from the shared Frontfriend stories with React Base, the same preview Studio shows. Styling comes from your Design System; the code below is native to Base UI.

Installation

Complete project setup first. Your configuration must select react/base and include your Design System ID.

pnpm exec frontfriend download form -f react --primitive base

Review dependencies reported by the downloader and keep your source changes in version control.

Usage

import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import {
  Form,
  FormField,
  FormItem,
  FormLabel,
  FormControl,
  FormMessage,
} from "@/components/ui/form";
import { cn } from "@frontfriend/sdk/cn";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
const FiltersSchema = z.object({
  login: z.string().min(2, { message: "Login must be at least 2 characters" }),
  password: z
    .string()
    .min(2, { message: "Password must be at least 2 characters" }),
});
function FormVertical({
  orientation,
}: {
  orientation?: "vertical" | "horizontal";
}) {
  const emptyValue = {
    login: "",
    string: "",
  };
  const form = useForm<z.infer<typeof FiltersSchema>>({
    resolver: zodResolver(FiltersSchema),
    defaultValues: emptyValue,
  });
  function onSubmit(values: z.infer<typeof FiltersSchema>) {
    console.log(values);
  }
  return (
    <div>
      <Form {...form}>
        <form
          onSubmit={form.handleSubmit(onSubmit)}
          className={cn(
            "flex gap-8 self-stretch w-full",
            orientation === "horizontal"
              ? "flex-row items-end"
              : "flex-col items-start",
          )}
          id="filters_form"
        >
          <FormField
            control={form.control}
            name="login"
            render={({ field }) => (
              <FormItem className="w-full">
                <FormLabel>Login</FormLabel>
                <FormControl>
                  <Input className="w-full" {...field} />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          <FormField
            control={form.control}
            name="password"
            render={({ field }) => (
              <FormItem className="w-full">
                <FormLabel>Password</FormLabel>
                <FormControl>
                  <Input className="w-full" {...field} />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          <Button
            type="submit"
            className={cn(orientation === "horizontal" ? "w-fit" : "w-full")}
            variant="main"
          >
            Submit
          </Button>
        </form>
      </Form>
    </div>
  );
}
<FormVertical orientation="vertical" />;

Examples

Default

import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import {
  Form,
  FormField,
  FormItem,
  FormLabel,
  FormControl,
  FormMessage,
} from "@/components/ui/form";
import { cn } from "@frontfriend/sdk/cn";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
const FiltersSchema = z.object({
  login: z.string().min(2, { message: "Login must be at least 2 characters" }),
  password: z
    .string()
    .min(2, { message: "Password must be at least 2 characters" }),
});
function FormVertical({
  orientation,
}: {
  orientation?: "vertical" | "horizontal";
}) {
  const emptyValue = {
    login: "",
    string: "",
  };
  const form = useForm<z.infer<typeof FiltersSchema>>({
    resolver: zodResolver(FiltersSchema),
    defaultValues: emptyValue,
  });
  function onSubmit(values: z.infer<typeof FiltersSchema>) {
    console.log(values);
  }
  return (
    <div>
      <Form {...form}>
        <form
          onSubmit={form.handleSubmit(onSubmit)}
          className={cn(
            "flex gap-8 self-stretch w-full",
            orientation === "horizontal"
              ? "flex-row items-end"
              : "flex-col items-start",
          )}
          id="filters_form"
        >
          <FormField
            control={form.control}
            name="login"
            render={({ field }) => (
              <FormItem className="w-full">
                <FormLabel>Login</FormLabel>
                <FormControl>
                  <Input className="w-full" {...field} />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          <FormField
            control={form.control}
            name="password"
            render={({ field }) => (
              <FormItem className="w-full">
                <FormLabel>Password</FormLabel>
                <FormControl>
                  <Input className="w-full" {...field} />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          <Button
            type="submit"
            className={cn(orientation === "horizontal" ? "w-fit" : "w-full")}
            variant="main"
          >
            Submit
          </Button>
        </form>
      </Form>
    </div>
  );
}
<FormVertical orientation="vertical" />;

Horizontal

import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import {
  Form,
  FormField,
  FormItem,
  FormLabel,
  FormControl,
  FormMessage,
} from "@/components/ui/form";
import { cn } from "@frontfriend/sdk/cn";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
const FiltersSchema = z.object({
  login: z.string().min(2, { message: "Login must be at least 2 characters" }),
  password: z
    .string()
    .min(2, { message: "Password must be at least 2 characters" }),
});
function FormVertical({
  orientation,
}: {
  orientation?: "vertical" | "horizontal";
}) {
  const emptyValue = {
    login: "",
    string: "",
  };
  const form = useForm<z.infer<typeof FiltersSchema>>({
    resolver: zodResolver(FiltersSchema),
    defaultValues: emptyValue,
  });
  function onSubmit(values: z.infer<typeof FiltersSchema>) {
    console.log(values);
  }
  return (
    <div>
      <Form {...form}>
        <form
          onSubmit={form.handleSubmit(onSubmit)}
          className={cn(
            "flex gap-8 self-stretch w-full",
            orientation === "horizontal"
              ? "flex-row items-end"
              : "flex-col items-start",
          )}
          id="filters_form"
        >
          <FormField
            control={form.control}
            name="login"
            render={({ field }) => (
              <FormItem className="w-full">
                <FormLabel>Login</FormLabel>
                <FormControl>
                  <Input className="w-full" {...field} />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          <FormField
            control={form.control}
            name="password"
            render={({ field }) => (
              <FormItem className="w-full">
                <FormLabel>Password</FormLabel>
                <FormControl>
                  <Input className="w-full" {...field} />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          <Button
            type="submit"
            className={cn(orientation === "horizontal" ? "w-fit" : "w-full")}
            variant="main"
          >
            Submit
          </Button>
        </form>
      </Form>
    </div>
  );
}
<FormVertical orientation="horizontal" />;

API reference

Frontfriend-owned props. Native HTML and inherited primitive props remain documented by Base UI.

FormItem

Prop Type Default Description
orientation "vertical" | "horizontal" "vertical"

FormLabel

Prop Type Default Description
variant "main" | "secondary" —

Configuration determines the actual classes and available styling hooks. See Theming & component configuration before introducing a repeated local override.