Form Components
Overview
The Form Components library provides a comprehensive set of form input components built on top of Chakra UI and React Hook Form. These components follow consistent patterns for validation, error handling, and accessibility, making form development more efficient and maintainable.
Component Categories
Input Components
- WPInput - Standard text input with validation
- WPTextField - Textarea component for multi-line text
- WPNumberInput - Numeric input with validation
- FormField - Generic form field wrapper
- WPPinInput - PIN code input component
Select Components
- WPSelect - Single/multi-select dropdown
- WPCreatableSelect - Select with ability to create new options
Date/Time Components
- WPDatepicker - Date picker with calendar interface
- WPTimePicker - Time selection component
Form Layout Components
- WPForm - Main form container
- WPFormHStack - Horizontal form layout
- WPFormStack - Vertical form layout
- FormLayout - Advanced form layout utilities
Advanced Components
- WPPasswordValidator - Password input with validation rules
- WPPhoneInput - International phone number input
- ActionModal - Modal component for form actions
- ExportModal - Specialized modal for data export forms
Architecture
Version Support
The library supports multiple versions of React Hook Form:
- V7 Components (
/V7/) - Latest React Hook Form (v7+) - V2 Components (
/V2/) - Legacy React Hook Form (v6) - Legacy Components - Original implementations
Hook Form Integration
All components are designed to work seamlessly with React Hook Form:
import { useForm } from 'react-hook-form';
import { WPInput } from 'components/Forms/WPInput';
function MyForm() {
const {
register,
handleSubmit,
control,
formState: { errors },
} = useForm();
return (
<form onSubmit={handleSubmit(onSubmit)}>
<WPInput
name='username'
label='Username'
register={register}
error={errors.username}
isRequired
/>
</form>
);
}
When to Use
WPInput
- Use for standard text inputs (name, email, etc.)
- When you need validation and error handling
- For controlled form inputs with React Hook Form
WPTextField
- Use for multi-line text input (comments, descriptions)
- When you need resizable text areas
- For longer content input
WPSelect
- Use for dropdown selections
- When you need multi-select functionality
- For searchable select options
WPDatepicker
- Use for date selection
- When you need date range selection
- For calendar-based date input
FormLayout Components
- Use
WPFormas the main form container - Use
WPFormHStackfor horizontal form layouts - Use
WPFormStackfor vertical form layouts
Common Props
Standard Form Props
All form components share these common props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| name | string | ✓ | - | Form field name |
| label | string | - | - | Field label |
| placeholder | string | - | - | Input placeholder text |
| isRequired | boolean | - | false | Whether field is required |
| isDisabled | boolean | - | false | Whether field is disabled |
| error | FieldError | - | - | Error object from React Hook Form |
| helperText | string | - | - | Helper text below input |
React Hook Form Props
| Prop | Type | Required | Description |
|---|---|---|---|
| register | Function | ✓ | React Hook Form register function |
| control | Control | ✓ | React Hook Form control object |
| rules | Object | - | Validation rules |
API Documentation
WPInput
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| name | string | ✓ | - | Input name |
| label | string | - | - | Input label |
| placeholder | string | - | - | Placeholder text |
| type | 'text' | 'password' | 'number' | - | 'text' | Input type |
| register | Function | ✓ | - | React Hook Form register |
| error | FieldError | - | - | Error object |
| isRequired | boolean | - | false | Required field |
| inputMode | string | - | 'text' | Input mode |
| LeftElement | ReactNode | - | - | Left input addon |
| RightElement | ReactNode | - | - | Right input addon |
| formLabelStyle | string | - | - | Label style |
| formLabelSize | string | - | - | Label size |
| helperText | string | - | - | Helper text |
Usage Example
import { WPInput } from 'components/Forms/WPInput';
<WPInput
name='email'
label='Email Address'
placeholder='Enter your email'
type='email'
register={register}
error={errors.email}
isRequired
helperText="We'll never share your email"
/>;
WPTextField
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| name | string | ✓ | - | Textarea name |
| label | string | - | - | Textarea label |
| placeholder | string | - | - | Placeholder text |
| register | Function | ✓ | - | React Hook Form register |
| error | FieldError | - | - | Error object |
| isRequired | boolean | - | false | Required field |
| rows | number | - | 3 | Number of rows |
| resize | string | - | 'vertical' | Resize behavior |
Usage Example
import { WPTextField } from 'components/Forms/WPTextField';
<WPTextField
name='description'
label='Description'
placeholder='Enter description'
register={register}
error={errors.description}
rows={5}
/>;
WPSelect
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| name | string | ✓ | - | Select name |
| label | string | - | - | Select label |
| options | Array | ✓ | - | Select options |
| control | Control | ✓ | - | React Hook Form control |
| placeholder | string | - | - | Placeholder text |
| isMulti | boolean | - | false | Enable multi-select |
| isSearchable | boolean | - | true | Enable search |
| isLoading | boolean | - | false | Loading state |
| isDisabled | boolean | - | false | Disabled state |
| rules | Object | - | - | Validation rules |
Usage Example
import { WPSelect } from 'components/Forms/WPSelect';
const options = [
{ value: 'option1', label: 'Option 1' },
{ value: 'option2', label: 'Option 2' },
];
<WPSelect
name='category'
label='Category'
options={options}
control={control}
placeholder='Select category'
isRequired
rules={{ required: 'Please select a category' }}
/>;
WPDatepicker
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| name | string | ✓ | - | Date picker name |
| label | string | - | - | Date picker label |
| control | Control | ✓ | - | React Hook Form control |
| placeholder | string | - | - | Placeholder text |
| isRequired | boolean | - | false | Required field |
| isClearable | boolean | - | false | Allow clearing |
| withPortal | boolean | - | false | Render in portal |
| showTimeSelect | boolean | - | false | Enable time selection |
| dateFormat | string | - | 'MM/dd/yyyy' | Date format |
| rules | Object | - | - | Validation rules |
Usage Example
import { WPDatepicker } from 'components/Forms/WPDatepicker';
<WPDatepicker
name='birthDate'
label='Birth Date'
control={control}
placeholder='Select date'
isClearable
rules={{ required: 'Please select a date' }}
/>;
FormLayout Components
WPForm
Main form container with consistent styling and spacing.
import { WPForm } from 'components/Forms/FormLayout';
<WPForm onSubmit={handleSubmit(onSubmit)}>{/* Form fields */}</WPForm>;
WPFormHStack
Horizontal form layout for side-by-side inputs.
import { WPFormHStack } from 'components/Forms/FormLayout';
<WPFormHStack>
<WPInput name='firstName' label='First Name' />
<WPInput name='lastName' label='Last Name' />
</WPFormHStack>;
WPFormStack
Vertical form layout for stacked inputs.
import { WPFormStack } from 'components/Forms/FormLayout';
<WPFormStack>
<WPInput name='email' label='Email' />
<WPInput name='password' label='Password' />
</WPFormStack>;
Advanced Components
WPPasswordValidator
Password input with built-in validation rules and strength indicator.
import { WPPasswordValidator } from 'components/Forms/WPPasswordValidator';
<WPPasswordValidator
name='password'
label='Password'
control={control}
rules={{
required: 'Password is required',
minLength: {
value: 8,
message: 'Password must be at least 8 characters',
},
}}
/>;
WPPhoneInput
International phone number input with country selection.
import { WPPhoneInput } from 'components/Forms/WP-PhoneInput';
<WPPhoneInput
name='phone'
label='Phone Number'
control={control}
defaultCountry='US'
placeholder='Enter phone number'
/>;
ActionModal
Modal component for form actions with consistent styling.
import ActionModal from 'components/Forms/Modals/ActionModal';
<ActionModal
isOpen={isOpen}
onClose={onClose}
heading='Edit Profile'
footer={<Button onClick={handleSubmit}>Save Changes</Button>}
>
<WPForm>{/* Form fields */}</WPForm>
</ActionModal>;
Form Patterns
Basic Form with Validation
import { useForm } from 'react-hook-form';
import { WPForm, WPInput, WPSelect, WPDatepicker } from 'components/Forms';
function UserForm() {
const {
register,
handleSubmit,
control,
formState: { errors },
} = useForm();
const onSubmit = data => {
console.log(data);
};
return (
<WPForm onSubmit={handleSubmit(onSubmit)}>
<WPInput
name='name'
label='Full Name'
register={register}
error={errors.name}
isRequired
/>
<WPSelect
name='role'
label='Role'
options={roleOptions}
control={control}
rules={{ required: 'Please select a role' }}
/>
<WPDatepicker
name='startDate'
label='Start Date'
control={control}
rules={{ required: 'Please select a start date' }}
/>
<Button type='submit'>Submit</Button>
</WPForm>
);
}
Form with Custom Validation
function CustomValidationForm() {
const {
register,
handleSubmit,
control,
formState: { errors },
watch,
} = useForm();
const password = watch('password');
return (
<WPForm onSubmit={handleSubmit(onSubmit)}>
<WPInput
name='email'
label='Email'
register={register}
error={errors.email}
rules={{
required: 'Email is required',
pattern: {
value: /\S+@\S+\.\S+/,
message: 'Invalid email format',
},
}}
/>
<WPInput
name='password'
label='Password'
type='password'
register={register}
error={errors.password}
rules={{
required: 'Password is required',
minLength: {
value: 8,
message: 'Password must be at least 8 characters',
},
}}
/>
<WPInput
name='confirmPassword'
label='Confirm Password'
type='password'
register={register}
error={errors.confirmPassword}
rules={{
required: 'Please confirm your password',
validate: value => value === password || 'Passwords do not match',
}}
/>
</WPForm>
);
}
Multi-step Form
function MultiStepForm() {
const [step, setStep] = useState(1);
const methods = useForm();
const nextStep = () => setStep(step + 1);
const prevStep = () => setStep(step - 1);
return (
<FormProvider {...methods}>
<WPForm>
{step === 1 && <Step1 onNext={nextStep} />}
{step === 2 && <Step2 onNext={nextStep} onPrev={prevStep} />}
{step === 3 && <Step3 onPrev={prevStep} />}
</WPForm>
</FormProvider>
);
}
Styling & Theming
Theme Integration
Form components use Chakra UI's theming system:
// Custom theme for form components
const formTheme = {
components: {
FormLabel: {
baseStyle: {
color: 'charcoal',
fontSize: 'sm',
fontWeight: 'medium',
},
},
Input: {
baseStyle: {
field: {
borderColor: 'gray.300',
_focus: {
borderColor: 'green.500',
},
},
},
},
},
};
Custom Styling
<WPInput
name='customField'
label='Custom Field'
register={register}
formLabelStyle='text-caps'
formLabelSize='lg'
sx={{
'& .chakra-input': {
borderRadius: 'md',
borderColor: 'blue.300',
},
}}
/>
Accessibility
ARIA Support
- All form components include proper ARIA labels
- Error messages are associated with inputs
- Required fields are properly marked
- Form validation provides screen reader feedback
Keyboard Navigation
- Tab navigation between form fields
- Enter key submits forms
- Escape key clears inputs (when applicable)
- Arrow keys navigate select options
Error Handling
- Error messages are announced to screen readers
- Invalid fields are marked with
aria-invalid - Error icons provide visual feedback
- Form validation prevents submission of invalid data
Migration Guide
From V6 to V7 React Hook Form
// V6 (Legacy)
import { WPInput } from 'components/Forms/WPInput';
<WPInput name='field' register={register} error={errors.field} />;
// V7 (Current)
import { WPInput } from 'components/Forms/V7/WPInput';
<WPInput
name='field'
control={control}
rules={{ required: 'Field is required' }}
/>;
Component Updates
- Replace
registerprop withcontrol - Move validation to
rulesprop - Update error handling patterns
- Use
useControllerfor custom components
Best Practices
- Consistent Validation: Use common validation rules across forms
- Error Handling: Provide clear, actionable error messages
- Loading States: Show loading indicators during form submission
- Accessibility: Test with screen readers and keyboard navigation
- Performance: Use React.memo for expensive form components
- Type Safety: Use TypeScript interfaces for form data
Testing
Unit Testing
import { render, screen } from '@testing-library/react';
import { useForm } from 'react-hook-form';
import { WPInput } from 'components/Forms/WPInput';
function TestForm() {
const {
register,
formState: { errors },
} = useForm();
return (
<WPInput
name='test'
label='Test Input'
register={register}
error={errors.test}
/>
);
}
test('renders input with label', () => {
render(<TestForm />);
expect(screen.getByLabelText('Test Input')).toBeInTheDocument();
});
Integration Testing
import { render, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('form submission with validation', async () => {
const user = userEvent.setup();
const onSubmit = jest.fn();
render(<MyForm onSubmit={onSubmit} />);
// Fill form
await user.type(screen.getByLabelText('Email'), 'test@example.com');
await user.click(screen.getByRole('button', { name: 'Submit' }));
expect(onSubmit).toHaveBeenCalledWith({
email: 'test@example.com',
});
});
Common Issues
Form Not Submitting
- Check that all required fields are filled
- Verify validation rules are correct
- Ensure form is wrapped in
<form>tag - Check for JavaScript errors in console
Validation Not Working
- Ensure
rulesprop is properly configured - Check React Hook Form version compatibility
- Verify
controlobject is passed correctly - Test validation rules independently
Styling Issues
- Check Chakra UI theme configuration
- Verify CSS-in-JS styles are applied
- Test responsive behavior
- Check for style conflicts
Performance Optimization
Lazy Loading
const WPDatepicker = lazy(() => import('components/Forms/WPDatepicker'));
<Suspense fallback={<Spinner />}>
<WPDatepicker name='date' control={control} />
</Suspense>;
Memoization
const MemoizedWPInput = memo(WPInput);
// Use in forms with many fields
<MemoizedWPInput name='field' register={register} error={errors.field} />;
Debounced Validation
const debouncedValidation = useMemo(() => debounce(validateField, 300), []);
<WPInput name='field' register={register} onChange={debouncedValidation} />;