Forms

PreviousNext

Premium inset form primitives with compact layouts, accessible controls, validation feedback, and restrained motion.

Create your account

A focused onboarding form with secure validation and compact inset surfaces.

Optional. Tell us what you want to build.

Required before creating an account.

Your information is securely reviewed before submission.

"use client";

import { zodResolver } from "@hookform/resolvers/zod";
import { Controller, useForm } from "react-hook-form";
import { z } from "zod";

import { Button } from "@/components/matos-ui/button";
import { CheckboxField } from "@/components/matos-ui/checkbox";
import { FormGrid, FormGridItem } from "@/components/matos-ui/form-grid";
import { FormSection } from "@/components/matos-ui/form-section";
import { InputField } from "@/components/matos-ui/input";
import { PasswordInput } from "@/components/matos-ui/password-input";
import { Select } from "@/components/matos-ui/select";
import { TextareaField } from "@/components/matos-ui/textarea";

const signupSchema = z
  .object({
    firstName: z.string().trim().min(2, "Enter your first name."),
    lastName: z.string().trim().min(2, "Enter your last name."),
    email: z.email("Enter a valid email address."),
    phone: z.string().trim().min(10, "Enter a valid phone number."),
    password: z
      .string()
      .min(8, "Use at least 8 characters.")
      .regex(/[A-Z]/, "Include one uppercase letter.")
      .regex(/[a-z]/, "Include one lowercase letter.")
      .regex(/\d/, "Include one number.")
      .regex(/[^A-Za-z0-9]/, "Include one special character."),
    confirmPassword: z.string(),
    role: z.string().min(1, "Select an area of interest."),
    note: z.string().max(220, "Use up to 220 characters.").optional(),
    terms: z.boolean().refine((value) => value, {
      message: "Accept the terms to continue.",
    }),
  })
  .refine((values) => values.password === values.confirmPassword, {
    message: "Passwords must match.",
    path: ["confirmPassword"],
  });

type SignupValues = z.infer<typeof signupSchema>;

const roleOptions = [
  {
    value: "design",
    label: "Design",
    description: "Systems, interaction and visual craft.",
  },
  {
    value: "engineering",
    label: "Engineering",
    description: "Product interfaces and implementation.",
  },
  {
    value: "product",
    label: "Product",
    description: "Discovery, delivery and strategy.",
  },
];

export function SignupFormDemo() {
  const {
    control,
    register,
    handleSubmit,
    formState: { dirtyFields, errors, isSubmitting, isSubmitSuccessful },
  } = useForm<SignupValues>({
    resolver: zodResolver(signupSchema),
    defaultValues: {
      firstName: "",
      lastName: "",
      email: "",
      phone: "",
      password: "",
      confirmPassword: "",
      role: "",
      note: "",
      terms: false,
    },
    mode: "onBlur",
  });

  async function onSubmit() {
    await new Promise((resolve) => window.setTimeout(resolve, 1000));
  }

  return (
    <form
      onSubmit={handleSubmit(onSubmit)}
      className="mx-auto w-full max-w-3xl"
      noValidate
    >
      <FormSection
        title="Create your account"
        description="A focused onboarding form with secure validation and compact inset surfaces."
        footer={
          <>
            <p className="text-xs text-muted-foreground" aria-live="polite">
              {isSubmitSuccessful
                ? "Account details validated successfully."
                : "Your information is securely reviewed before submission."}
            </p>
            <Button
              type="submit"
              loading={isSubmitting}
              loadingText="Creating account..."
            >
              Create account
            </Button>
          </>
        }
      >
        <FormGrid gap="compact">
          <InputField
            label="First name"
            placeholder="Sofia"
            required
            error={errors.firstName?.message}
            {...register("firstName")}
          />
          <InputField
            label="Last name"
            placeholder="Matos"
            required
            error={errors.lastName?.message}
            {...register("lastName")}
          />
          <InputField
            type="email"
            label="Email"
            placeholder="sofia@company.com"
            required
            state={dirtyFields.email && !errors.email ? "success" : "default"}
            error={errors.email?.message}
            {...register("email")}
          />
          <InputField
            type="tel"
            label="Phone"
            placeholder="+55 11 99999-9999"
            required
            error={errors.phone?.message}
            {...register("phone")}
          />
          <Controller
            name="password"
            control={control}
            render={({ field }) => (
              <PasswordInput
                label="Password"
                required
                value={field.value}
                name={field.name}
                onBlur={field.onBlur}
                onChange={field.onChange}
                ref={field.ref}
                error={errors.password?.message}
                placeholder="Create a password"
              />
            )}
          />
          <Controller
            name="confirmPassword"
            control={control}
            render={({ field }) => (
              <PasswordInput
                label="Confirm password"
                required
                value={field.value}
                name={field.name}
                onBlur={field.onBlur}
                onChange={field.onChange}
                ref={field.ref}
                error={errors.confirmPassword?.message}
                placeholder="Repeat your password"
                showCriteria={false}
              />
            )}
          />
          <FormGridItem span="full">
            <Controller
              name="role"
              control={control}
              render={({ field }) => (
                <Select
                  label="Role or interest"
                  required
                  options={roleOptions}
                  value={field.value || null}
                  onValueChange={(nextValue) => field.onChange(nextValue ?? "")}
                  name={field.name}
                  inputRef={field.ref}
                  error={errors.role?.message}
                  placeholder="Choose an area"
                />
              )}
            />
          </FormGridItem>
          <FormGridItem span="full">
            <TextareaField
              label="Additional notes"
              description="Optional. Tell us what you want to build."
              placeholder="A short note about your next product..."
              error={errors.note?.message}
              {...register("note")}
            />
          </FormGridItem>
          <FormGridItem span="full">
            <Controller
              name="terms"
              control={control}
              render={({ field }) => (
                <CheckboxField
                  label="I agree to the workspace terms"
                  description="Required before creating an account."
                  checked={field.value}
                  onCheckedChange={field.onChange}
                  name={field.name}
                  inputRef={field.ref}
                  error={errors.terms?.message}
                  required
                />
              )}
            />
          </FormGridItem>
        </FormGrid>
      </FormSection>
    </form>
  );
}

Inset Forms

Forms is a composable collection for polished product workflows. Its controls use theme tokens, soft inset surfaces, consistent focus feedback, and small state transitions that remain practical in production.

The signup example combines Field, Input, PasswordInput, Select, Textarea, Checkbox, FormGrid, and FormSection with React Hook Form and Zod validation.

Included Components

Install The Example

pnpm dlx shadcn@latest add https://matos-ui.com/r/signup-form-demo.json