CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-nextui-org--react

Beautiful and modern React UI library with comprehensive components, theming, and accessibility support.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

forms.mddocs/

Form Integration

NextUI provides comprehensive form handling capabilities with the Form component and seamless integration patterns for validation, data collection, and form state management.

Capabilities

Form Component

A form container component that provides validation context, error handling, and integration with form libraries for building robust form interfaces.

interface FormProps {
  /** Form content and input elements */
  children?: React.ReactNode;
  /** Server-side or external validation errors */
  validationErrors?: ValidationErrors;
  /** Validation behavior mode */
  validationBehavior?: "aria" | "native";
  /** Custom CSS class */
  className?: string;
  /** Form reset handler */
  onReset?: () => void;
  /** Form submission handler */
  onSubmit?: (e: React.FormEvent<HTMLFormElement>) => void;
  /** Invalid submission handler */
  onInvalidSubmit?: (errors: ValidationErrors) => void;
}

interface ValidationErrors {
  [fieldName: string]: ValidationError;
}

type ValidationError = string | string[];

function Form(props: FormProps): JSX.Element;

Basic Form Usage:

import { 
  Form, Input, Button, Checkbox, Select, SelectItem,
  Card, CardHeader, CardBody, CardFooter
} from "@nextui-org/react";

function BasicFormExample() {
  const [formData, setFormData] = useState({
    name: "",
    email: "",
    password: "",
    confirmPassword: "",
    terms: false,
    country: "",
  });

  const [errors, setErrors] = useState<ValidationErrors>({});

  const validateForm = () => {
    const newErrors: ValidationErrors = {};

    if (!formData.name.trim()) {
      newErrors.name = "Name is required";
    }

    if (!formData.email.trim()) {
      newErrors.email = "Email is required";
    } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
      newErrors.email = "Please enter a valid email";
    }

    if (!formData.password) {
      newErrors.password = "Password is required";
    } else if (formData.password.length < 8) {
      newErrors.password = "Password must be at least 8 characters";
    }

    if (formData.password !== formData.confirmPassword) {
      newErrors.confirmPassword = "Passwords do not match";
    }

    if (!formData.terms) {
      newErrors.terms = "You must accept the terms and conditions";
    }

    return newErrors;
  };

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const validationErrors = validateForm();
    setErrors(validationErrors);

    if (Object.keys(validationErrors).length === 0) {
      console.log("Form submitted successfully:", formData);
      // Handle successful submission
    }
  };

  const updateField = (field: string) => (value: string | boolean) => {
    setFormData(prev => ({ ...prev, [field]: value }));
    // Clear error when user starts typing
    if (errors[field]) {
      setErrors(prev => ({ ...prev, [field]: undefined }));
    }
  };

  return (
    <Card className="max-w-md mx-auto">
      <CardHeader>
        <h2 className="text-xl font-bold">Create Account</h2>
      </CardHeader>
      <Form onSubmit={handleSubmit} validationErrors={errors}>
        <CardBody className="space-y-4">
          <Input
            label="Full Name"
            placeholder="Enter your full name"
            value={formData.name}
            onValueChange={updateField("name")}
            isRequired
            isInvalid={!!errors.name}
            errorMessage={errors.name}
          />

          <Input
            type="email"
            label="Email"
            placeholder="Enter your email"
            value={formData.email}
            onValueChange={updateField("email")}
            isRequired
            isInvalid={!!errors.email}
            errorMessage={errors.email}
          />

          <Input
            type="password"
            label="Password"
            placeholder="Enter your password"
            value={formData.password}
            onValueChange={updateField("password")}
            isRequired
            isInvalid={!!errors.password}
            errorMessage={errors.password}
          />

          <Input
            type="password"
            label="Confirm Password"
            placeholder="Confirm your password"
            value={formData.confirmPassword}
            onValueChange={updateField("confirmPassword")}
            isRequired
            isInvalid={!!errors.confirmPassword}
            errorMessage={errors.confirmPassword}
          />

          <Select
            label="Country"
            placeholder="Select your country"
            selectedKeys={formData.country ? [formData.country] : []}
            onSelectionChange={(keys) => {
              const selected = Array.from(keys)[0] as string;
              updateField("country")(selected);
            }}
            isRequired
          >
            <SelectItem key="us">United States</SelectItem>
            <SelectItem key="ca">Canada</SelectItem>
            <SelectItem key="uk">United Kingdom</SelectItem>
            <SelectItem key="de">Germany</SelectItem>
            <SelectItem key="fr">France</SelectItem>
          </Select>

          <Checkbox
            isSelected={formData.terms}
            onValueChange={updateField("terms")}
            isInvalid={!!errors.terms}
            color={errors.terms ? "danger" : "primary"}
          >
            I agree to the{" "}
            <a href="#" className="text-primary hover:underline">
              terms and conditions
            </a>
          </Checkbox>
          
          {errors.terms && (
            <p className="text-danger text-sm mt-1">{errors.terms}</p>
          )}
        </CardBody>
        <CardFooter>
          <Button 
            type="submit" 
            color="primary" 
            className="w-full"
            size="lg"
          >
            Create Account
          </Button>
        </CardFooter>
      </Form>
    </Card>
  );
}

Form Context

Context system for sharing form state and validation across form components.

interface FormContext {
  /** Current validation errors */
  validationErrors?: ValidationErrors;
  /** Validation behavior mode */
  validationBehavior?: "aria" | "native";
  /** Form submission state */
  isSubmitting?: boolean;
  /** Form dirty state */
  isDirty?: boolean;
  /** Form valid state */
  isValid?: boolean;
  /** Reset form */
  reset?: () => void;
  /** Update field validation */
  updateValidation?: (fieldName: string, error: ValidationError | null) => void;
}

/**
 * Hook to access form context
 */
function useSlottedContext<T>(context: React.Context<T>): T | undefined;

React Hook Form Integration

NextUI components integrate seamlessly with React Hook Form for advanced form handling.

import { useForm, Controller, SubmitHandler } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { 
  Form, Input, Button, Select, SelectItem, Checkbox,
  Card, CardHeader, CardBody, CardFooter
} from "@nextui-org/react";

// Validation schema
const schema = z.object({
  firstName: z.string().min(1, "First name is required"),
  lastName: z.string().min(1, "Last name is required"),
  email: z.string().email("Please enter a valid email"),
  age: z.number().min(18, "Must be at least 18 years old"),
  country: z.string().min(1, "Please select a country"),
  newsletter: z.boolean(),
  bio: z.string().max(500, "Bio must be less than 500 characters").optional(),
});

type FormData = z.infer<typeof schema>;

function ReactHookFormExample() {
  const {
    control,
    handleSubmit,
    reset,
    formState: { errors, isSubmitting, isDirty, isValid }
  } = useForm<FormData>({
    resolver: zodResolver(schema),
    defaultValues: {
      firstName: "",
      lastName: "",
      email: "",
      age: 18,
      country: "",
      newsletter: false,
      bio: "",
    },
    mode: "onChange", // Validate on change
  });

  const onSubmit: SubmitHandler<FormData> = async (data) => {
    try {
      // Simulate API call
      await new Promise(resolve => setTimeout(resolve, 1000));
      console.log("Form submitted:", data);
      reset();
    } catch (error) {
      console.error("Submission error:", error);
    }
  };

  return (
    <Card className="max-w-2xl mx-auto">
      <CardHeader>
        <h2 className="text-xl font-bold">User Profile</h2>
      </CardHeader>
      <form onSubmit={handleSubmit(onSubmit)}>
        <CardBody className="space-y-4">
          <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
            <Controller
              name="firstName"
              control={control}
              render={({ field, fieldState }) => (
                <Input
                  {...field}
                  label="First Name"
                  placeholder="Enter your first name"
                  isInvalid={fieldState.invalid}
                  errorMessage={fieldState.error?.message}
                />
              )}
            />

            <Controller
              name="lastName"
              control={control}
              render={({ field, fieldState }) => (
                <Input
                  {...field}
                  label="Last Name"
                  placeholder="Enter your last name"
                  isInvalid={fieldState.invalid}
                  errorMessage={fieldState.error?.message}
                />
              )}
            />
          </div>

          <Controller
            name="email"
            control={control}
            render={({ field, fieldState }) => (
              <Input
                {...field}
                type="email"
                label="Email"
                placeholder="Enter your email"
                isInvalid={fieldState.invalid}
                errorMessage={fieldState.error?.message}
              />
            )}
          />

          <Controller
            name="age"
            control={control}
            render={({ field, fieldState }) => (
              <Input
                {...field}
                type="number"
                label="Age"
                placeholder="Enter your age"
                value={field.value?.toString() || ""}
                onChange={(e) => field.onChange(parseInt(e.target.value) || 0)}
                isInvalid={fieldState.invalid}
                errorMessage={fieldState.error?.message}
              />
            )}
          />

          <Controller
            name="country"
            control={control}
            render={({ field, fieldState }) => (
              <Select
                {...field}
                label="Country"
                placeholder="Select your country"
                selectedKeys={field.value ? [field.value] : []}
                onSelectionChange={(keys) => {
                  const selected = Array.from(keys)[0] as string;
                  field.onChange(selected);
                }}
                isInvalid={fieldState.invalid}
                errorMessage={fieldState.error?.message}
              >
                <SelectItem key="us">United States</SelectItem>
                <SelectItem key="ca">Canada</SelectItem>
                <SelectItem key="uk">United Kingdom</SelectItem>
                <SelectItem key="de">Germany</SelectItem>
                <SelectItem key="fr">France</SelectItem>
              </Select>
            )}
          />

          <Controller
            name="bio"
            control={control}
            render={({ field, fieldState }) => (
              <Textarea
                {...field}
                label="Bio"
                placeholder="Tell us about yourself (optional)"
                maxRows={3}
                isInvalid={fieldState.invalid}
                errorMessage={fieldState.error?.message}
              />
            )}
          />

          <Controller
            name="newsletter"
            control={control}
            render={({ field }) => (
              <Checkbox
                isSelected={field.value}
                onValueChange={field.onChange}
                color="primary"
              >
                Subscribe to newsletter
              </Checkbox>
            )}
          />
        </CardBody>
        <CardFooter className="flex gap-2">
          <Button
            type="button"
            variant="flat"
            onPress={() => reset()}
            isDisabled={!isDirty}
          >
            Reset
          </Button>
          <Button
            type="submit"
            color="primary"
            isLoading={isSubmitting}
            isDisabled={!isValid}
            className="flex-1"
          >
            {isSubmitting ? "Saving..." : "Save Profile"}
          </Button>
        </CardFooter>
      </form>
    </Card>
  );
}

Formik Integration

NextUI components also work seamlessly with Formik for form state management.

import { Formik, Form as FormikForm, Field } from "formik";
import * as Yup from "yup";
import { 
  Input, Button, Select, SelectItem, Textarea,
  Card, CardHeader, CardBody, CardFooter
} from "@nextui-org/react";

// Validation schema
const validationSchema = Yup.object({
  name: Yup.string().required("Name is required"),
  email: Yup.string().email("Invalid email").required("Email is required"),
  message: Yup.string()
    .min(10, "Message must be at least 10 characters")
    .required("Message is required"),
  priority: Yup.string().required("Please select a priority"),
});

interface ContactFormValues {
  name: string;
  email: string;
  message: string;
  priority: string;
}

function FormikExample() {
  const initialValues: ContactFormValues = {
    name: "",
    email: "",
    message: "",
    priority: "",
  };

  const handleSubmit = async (
    values: ContactFormValues,
    { setSubmitting, resetForm }: any
  ) => {
    try {
      // Simulate API call
      await new Promise(resolve => setTimeout(resolve, 1000));
      console.log("Contact form submitted:", values);
      resetForm();
    } catch (error) {
      console.error("Submission error:", error);
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <Card className="max-w-lg mx-auto">
      <CardHeader>
        <h2 className="text-xl font-bold">Contact Us</h2>
      </CardHeader>
      <Formik
        initialValues={initialValues}
        validationSchema={validationSchema}
        onSubmit={handleSubmit}
      >
        {({ isSubmitting, errors, touched, setFieldValue, values }) => (
          <FormikForm>
            <CardBody className="space-y-4">
              <Field name="name">
                {({ field, meta }: any) => (
                  <Input
                    {...field}
                    label="Name"
                    placeholder="Enter your name"
                    isInvalid={!!(meta.touched && meta.error)}
                    errorMessage={meta.touched && meta.error}
                  />
                )}
              </Field>

              <Field name="email">
                {({ field, meta }: any) => (
                  <Input
                    {...field}
                    type="email"
                    label="Email"
                    placeholder="Enter your email"
                    isInvalid={!!(meta.touched && meta.error)}
                    errorMessage={meta.touched && meta.error}
                  />
                )}
              </Field>

              <Field name="priority">
                {({ meta }: any) => (
                  <Select
                    label="Priority"
                    placeholder="Select priority level"
                    selectedKeys={values.priority ? [values.priority] : []}
                    onSelectionChange={(keys) => {
                      const selected = Array.from(keys)[0] as string;
                      setFieldValue("priority", selected);
                    }}
                    isInvalid={!!(meta.touched && meta.error)}
                    errorMessage={meta.touched && meta.error}
                  >
                    <SelectItem key="low">Low</SelectItem>
                    <SelectItem key="medium">Medium</SelectItem>
                    <SelectItem key="high">High</SelectItem>
                    <SelectItem key="urgent">Urgent</SelectItem>
                  </Select>
                )}
              </Field>

              <Field name="message">
                {({ field, meta }: any) => (
                  <Textarea
                    {...field}
                    label="Message"
                    placeholder="Enter your message"
                    minRows={4}
                    isInvalid={!!(meta.touched && meta.error)}
                    errorMessage={meta.touched && meta.error}
                  />
                )}
              </Field>
            </CardBody>
            <CardFooter>
              <Button
                type="submit"
                color="primary"
                isLoading={isSubmitting}
                className="w-full"
                size="lg"
              >
                {isSubmitting ? "Sending..." : "Send Message"}
              </Button>
            </CardFooter>
          </FormikForm>
        )}
      </Formik>
    </Card>
  );
}

Field Validation Patterns

Common validation patterns and utilities for form fields.

import { 
  Input, DatePicker, Select, SelectItem, Checkbox,
  Button, Card, CardBody
} from "@nextui-org/react";
import { CalendarDate, today, getLocalTimeZone } from "@internationalized/date";

// Validation utilities
const validators = {
  required: (value: any) => {
    if (!value || (typeof value === "string" && !value.trim())) {
      return "This field is required";
    }
    return true;
  },

  email: (value: string) => {
    if (value && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
      return "Please enter a valid email address";
    }
    return true;
  },

  minLength: (min: number) => (value: string) => {
    if (value && value.length < min) {
      return `Must be at least ${min} characters`;
    }
    return true;
  },

  maxLength: (max: number) => (value: string) => {
    if (value && value.length > max) {
      return `Must be no more than ${max} characters`;
    }
    return true;
  },

  phone: (value: string) => {
    if (value && !/^\+?[\d\s\-\(\)]+$/.test(value)) {
      return "Please enter a valid phone number";
    }
    return true;
  },

  url: (value: string) => {
    if (value) {
      try {
        new URL(value);
      } catch {
        return "Please enter a valid URL";
      }
    }
    return true;
  },

  futureDate: (value: CalendarDate | null) => {
    if (value && value.compare(today(getLocalTimeZone())) <= 0) {
      return "Date must be in the future";
    }
    return true;
  },

  passwordStrength: (value: string) => {
    if (value) {
      const hasLower = /[a-z]/.test(value);
      const hasUpper = /[A-Z]/.test(value);
      const hasNumber = /\d/.test(value);
      const hasSymbol = /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(value);
      const isLongEnough = value.length >= 8;

      if (!isLongEnough) return "Password must be at least 8 characters";
      if (!hasLower) return "Password must contain lowercase letters";
      if (!hasUpper) return "Password must contain uppercase letters"; 
      if (!hasNumber) return "Password must contain numbers";
      if (!hasSymbol) return "Password must contain symbols";
    }
    return true;
  },

  confirmPassword: (original: string) => (value: string) => {
    if (value && value !== original) {
      return "Passwords do not match";
    }
    return true;
  },
};

// Validation runner utility
const runValidations = (value: any, validationFns: ((value: any) => boolean | string)[]) => {
  for (const validate of validationFns) {
    const result = validate(value);
    if (result !== true) {
      return result;
    }
  }
  return true;
};

function ValidationPatternsExample() {
  const [formData, setFormData] = useState({
    email: "",
    password: "",
    confirmPassword: "",
    website: "",
    birthDate: null as CalendarDate | null,
    phone: "",
    terms: false,
  });

  const [errors, setErrors] = useState<Record<string, string>>({});

  const validateField = (name: string, value: any) => {
    let result: boolean | string = true;

    switch (name) {
      case "email":
        result = runValidations(value, [
          validators.required,
          validators.email,
        ]);
        break;
      case "password":
        result = runValidations(value, [
          validators.required,
          validators.passwordStrength,
        ]);
        break;
      case "confirmPassword":
        result = runValidations(value, [
          validators.required,
          validators.confirmPassword(formData.password),
        ]);
        break;
      case "website":
        result = runValidations(value, [validators.url]);
        break;
      case "birthDate":
        result = runValidations(value, [validators.futureDate]);
        break;
      case "phone":
        result = runValidations(value, [validators.phone]);
        break;
      case "terms":
        result = value ? true : "You must accept the terms";
        break;
    }

    setErrors(prev => ({
      ...prev,
      [name]: result === true ? "" : result
    }));

    return result === true;
  };

  const updateField = (name: string) => (value: any) => {
    setFormData(prev => ({ ...prev, [name]: value }));
    validateField(name, value);
  };

  return (
    <Card className="max-w-lg mx-auto">
      <CardBody className="space-y-4">
        <Input
          type="email"
          label="Email Address"
          placeholder="Enter your email"
          value={formData.email}
          onValueChange={updateField("email")}
          isInvalid={!!errors.email}
          errorMessage={errors.email}
          isRequired
        />

        <Input
          type="password"
          label="Password"
          placeholder="Create a strong password"
          value={formData.password}
          onValueChange={updateField("password")}
          isInvalid={!!errors.password}
          errorMessage={errors.password}
          description="Must contain uppercase, lowercase, numbers, and symbols"
          isRequired
        />

        <Input
          type="password"
          label="Confirm Password"
          placeholder="Confirm your password"
          value={formData.confirmPassword}
          onValueChange={updateField("confirmPassword")}
          isInvalid={!!errors.confirmPassword}
          errorMessage={errors.confirmPassword}
          isRequired
        />

        <Input
          type="url"
          label="Website (Optional)"
          placeholder="https://example.com"
          value={formData.website}
          onValueChange={updateField("website")}
          isInvalid={!!errors.website}
          errorMessage={errors.website}
        />

        <Input
          type="tel"
          label="Phone Number (Optional)"
          placeholder="+1 (555) 123-4567"
          value={formData.phone}
          onValueChange={updateField("phone")}
          isInvalid={!!errors.phone}
          errorMessage={errors.phone}
        />

        <DatePicker
          label="Event Date"
          value={formData.birthDate}
          onChange={updateField("birthDate")}
          minValue={today(getLocalTimeZone()).add({ days: 1 })}
          isInvalid={!!errors.birthDate}
          errorMessage={errors.birthDate}
          description="Select a future date"
        />

        <Checkbox
          isSelected={formData.terms}
          onValueChange={updateField("terms")}
          isInvalid={!!errors.terms}
          color={errors.terms ? "danger" : "primary"}
        >
          I agree to the terms and conditions
        </Checkbox>
        
        {errors.terms && (
          <p className="text-danger text-sm">{errors.terms}</p>
        )}

        <Button 
          color="primary" 
          className="w-full"
          isDisabled={Object.values(errors).some(error => !!error) || !formData.terms}
        >
          Submit Form
        </Button>
      </CardBody>
    </Card>
  );
}

Multi-Step Form Pattern

Building complex multi-step forms with NextUI components.

import { 
  Card, CardHeader, CardBody, CardFooter,
  Button, Input, Select, SelectItem, DatePicker, Textarea,
  Progress, Divider
} from "@nextui-org/react";
import { CalendarDate } from "@internationalized/date";

interface StepData {
  // Step 1: Personal Info
  firstName: string;
  lastName: string;
  email: string;
  phone: string;
  birthDate: CalendarDate | null;
  
  // Step 2: Address
  address: string;
  city: string;
  state: string;
  zipCode: string;
  country: string;
  
  // Step 3: Preferences  
  interests: string[];
  bio: string;
  newsletter: boolean;
}

function MultiStepFormExample() {
  const [currentStep, setCurrentStep] = useState(1);
  const [formData, setFormData] = useState<StepData>({
    firstName: "", lastName: "", email: "", phone: "", birthDate: null,
    address: "", city: "", state: "", zipCode: "", country: "",
    interests: [], bio: "", newsletter: false,
  });
  const [errors, setErrors] = useState<Record<string, string>>({});

  const totalSteps = 3;
  const progress = (currentStep / totalSteps) * 100;

  const validateStep = (step: number): boolean => {
    const newErrors: Record<string, string> = {};

    switch (step) {
      case 1:
        if (!formData.firstName.trim()) newErrors.firstName = "First name is required";
        if (!formData.lastName.trim()) newErrors.lastName = "Last name is required";
        if (!formData.email.trim()) newErrors.email = "Email is required";
        break;
      case 2:
        if (!formData.address.trim()) newErrors.address = "Address is required";
        if (!formData.city.trim()) newErrors.city = "City is required";
        if (!formData.country) newErrors.country = "Country is required";
        break;
      case 3:
        // Optional validation for final step
        break;
    }

    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };

  const nextStep = () => {
    if (validateStep(currentStep)) {
      setCurrentStep(prev => Math.min(prev + 1, totalSteps));
    }
  };

  const prevStep = () => {
    setCurrentStep(prev => Math.max(prev - 1, 1));
  };

  const handleSubmit = () => {
    if (validateStep(currentStep)) {
      console.log("Form submitted:", formData);
      // Handle final submission
    }
  };

  const updateField = (field: keyof StepData) => (value: any) => {
    setFormData(prev => ({ ...prev, [field]: value }));
    if (errors[field]) {
      setErrors(prev => ({ ...prev, [field]: "" }));
    }
  };

  const renderStep = () => {
    switch (currentStep) {
      case 1:
        return (
          <div className="space-y-4">
            <div className="grid grid-cols-2 gap-4">
              <Input
                label="First Name"
                placeholder="Enter first name"
                value={formData.firstName}
                onValueChange={updateField("firstName")}
                isInvalid={!!errors.firstName}
                errorMessage={errors.firstName}
                isRequired
              />
              <Input
                label="Last Name"
                placeholder="Enter last name"
                value={formData.lastName}
                onValueChange={updateField("lastName")}
                isInvalid={!!errors.lastName}
                errorMessage={errors.lastName}
                isRequired
              />
            </div>
            <Input
              type="email"
              label="Email"
              placeholder="Enter your email"
              value={formData.email}
              onValueChange={updateField("email")}
              isInvalid={!!errors.email}
              errorMessage={errors.email}
              isRequired
            />
            <Input
              type="tel"
              label="Phone Number"
              placeholder="Enter phone number"
              value={formData.phone}
              onValueChange={updateField("phone")}
            />
            <DatePicker
              label="Birth Date"
              value={formData.birthDate}
              onChange={updateField("birthDate")}
            />
          </div>
        );

      case 2:
        return (
          <div className="space-y-4">
            <Input
              label="Street Address"
              placeholder="Enter your address"
              value={formData.address}
              onValueChange={updateField("address")}
              isInvalid={!!errors.address}
              errorMessage={errors.address}
              isRequired
            />
            <div className="grid grid-cols-2 gap-4">
              <Input
                label="City"
                placeholder="Enter city"
                value={formData.city}
                onValueChange={updateField("city")}
                isInvalid={!!errors.city}
                errorMessage={errors.city}
                isRequired
              />
              <Input
                label="State/Province"
                placeholder="Enter state"
                value={formData.state}
                onValueChange={updateField("state")}
              />
            </div>
            <div className="grid grid-cols-2 gap-4">
              <Input
                label="ZIP Code"
                placeholder="Enter ZIP code"
                value={formData.zipCode}
                onValueChange={updateField("zipCode")}
              />
              <Select
                label="Country"
                placeholder="Select country"
                selectedKeys={formData.country ? [formData.country] : []}
                onSelectionChange={(keys) => {
                  const selected = Array.from(keys)[0] as string;
                  updateField("country")(selected);
                }}
                isInvalid={!!errors.country}
                errorMessage={errors.country}
                isRequired
              >
                <SelectItem key="us">United States</SelectItem>
                <SelectItem key="ca">Canada</SelectItem>
                <SelectItem key="uk">United Kingdom</SelectItem>
              </Select>
            </div>
          </div>
        );

      case 3:
        return (
          <div className="space-y-4">
            <Textarea
              label="Bio (Optional)"
              placeholder="Tell us about yourself"
              value={formData.bio}
              onValueChange={updateField("bio")}
              maxRows={4}
            />
            <div className="text-center">
              <p>Review your information and submit when ready.</p>
            </div>
          </div>
        );

      default:
        return null;
    }
  };

  return (
    <Card className="max-w-2xl mx-auto">
      <CardHeader className="space-y-2">
        <div className="flex justify-between items-center w-full">
          <h2 className="text-xl font-bold">Registration Form</h2>
          <span className="text-sm text-default-500">
            Step {currentStep} of {totalSteps}
          </span>
        </div>
        <Progress 
          value={progress} 
          color="primary" 
          className="w-full"
          showValueLabel={false}
        />
      </CardHeader>

      <CardBody>
        {renderStep()}
      </CardBody>

      <Divider />

      <CardFooter className="flex justify-between">
        <Button
          variant="flat"
          onPress={prevStep}
          isDisabled={currentStep === 1}
        >
          Previous
        </Button>

        {currentStep < totalSteps ? (
          <Button color="primary" onPress={nextStep}>
            Next Step
          </Button>
        ) : (
          <Button color="success" onPress={handleSubmit}>
            Submit Form
          </Button>
        )}
      </CardFooter>
    </Card>
  );
}

Form Integration Types

// Form validation types
type ValidationError = string | string[];

interface ValidationErrors {
  [fieldName: string]: ValidationError;
}

interface ValidationResult {
  isInvalid: boolean;
  validationErrors: string[];
  validationDetails: ValidationDetails;
}

interface ValidationDetails {
  [key: string]: any;
}

// Form state types
interface FormState {
  values: Record<string, any>;
  errors: ValidationErrors;
  touched: Record<string, boolean>;
  isSubmitting: boolean;
  isValidating: boolean;
  isDirty: boolean;
  isValid: boolean;
  submitCount: number;
}

// Form field types
interface FieldProps<T = any> {
  name: string;
  value: T;
  onChange: (value: T) => void;
  onBlur?: () => void;
  error?: string;
  isInvalid?: boolean;
  isDisabled?: boolean;
  isRequired?: boolean;
}

// Validation function types
type FieldValidator<T = any> = (value: T) => boolean | string | Promise<boolean | string>;
type FormValidator<T = Record<string, any>> = (values: T) => ValidationErrors | Promise<ValidationErrors>;

// Form hook return types
interface UseFormReturn<T = Record<string, any>> {
  values: T;
  errors: ValidationErrors;
  touched: Record<keyof T, boolean>;
  isSubmitting: boolean;
  isValid: boolean;
  isDirty: boolean;
  
  setFieldValue: (field: keyof T, value: any) => void;
  setFieldError: (field: keyof T, error: string | null) => void;
  setFieldTouched: (field: keyof T, touched?: boolean) => void;
  
  validateField: (field: keyof T) => Promise<boolean>;
  validateForm: () => Promise<boolean>;
  
  handleSubmit: (onSubmit: (values: T) => void | Promise<void>) => (e?: React.FormEvent) => void;
  handleReset: () => void;
  
  getFieldProps: (name: keyof T) => FieldProps;
}

// Multi-step form types
interface StepConfig {
  id: string;
  title: string;
  fields: string[];
  validation?: FormValidator;
  optional?: boolean;
}

interface MultiStepFormState {
  currentStep: number;
  totalSteps: number;
  completedSteps: number[];
  canGoNext: boolean;
  canGoPrevious: boolean;
  isFirstStep: boolean;
  isLastStep: boolean;
}

Integration Best Practices

Form Accessibility

Ensuring forms are accessible and follow ARIA best practices.

// Accessibility patterns for forms
const AccessibleFormExample = () => {
  return (
    <form role="form" aria-label="Contact form">
      <fieldset>
        <legend className="text-lg font-semibold mb-4">Personal Information</legend>
        
        <Input
          label="Full Name"
          placeholder="Enter your full name"
          isRequired
          description="This will be used as your display name"
          // Automatically gets proper ARIA attributes
        />
        
        <Input
          type="email"
          label="Email Address"
          placeholder="Enter your email"
          isRequired
          validate={(value) => {
            if (!value) return "Email is required";
            if (!/\S+@\S+\.\S+/.test(value)) return "Invalid email format";
            return true;
          }}
          // Error messages are automatically announced by screen readers
        />
      </fieldset>
      
      <Button 
        type="submit"
        aria-describedby="submit-help"
      >
        Submit Form
      </Button>
      
      <p id="submit-help" className="text-sm text-default-500 mt-2">
        Your information will be processed securely
      </p>
    </form>
  );
};

Performance Optimization

Optimizing form performance for large forms with many fields.

// Debounced validation for better performance
import { useDeferredValue, useCallback } from "react";
import { debounce } from "lodash";

const useOptimizedValidation = (validator: FieldValidator, delay = 300) => {
  const debouncedValidator = useCallback(
    debounce(validator, delay),
    [validator, delay]
  );

  return debouncedValidator;
};

// Memoized form fields to prevent unnecessary re-renders
const MemoizedFormField = React.memo(({ field, value, onChange, error }: any) => {
  return (
    <Input
      label={field.label}
      value={value}
      onValueChange={onChange}
      isInvalid={!!error}
      errorMessage={error}
      {...field.props}
    />
  );
});

docs

core-system.md

data-display.md

date-time.md

feedback.md

forms.md

index.md

inputs.md

interactions.md

layout.md

navigation.md

overlays.md

utilities.md

tile.json