Beautiful and modern React UI library with comprehensive components, theming, and accessibility support.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
NextUI provides comprehensive form handling capabilities with the Form component and seamless integration patterns for validation, data collection, and form state management.
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>
);
}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;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>
);
}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>
);
}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>
);
}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 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;
}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>
);
};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}
/>
);
});