Form Design
- Group related inputs logically and use consistent spacing
- Provide clear labels and helpful placeholder text
- Use appropriate input types (email, phone, etc.) for better UX
- Implement real-time validation with clear error messages
Input provides a versatile text input solution with built-in animations, validation states, floating labels, and specialized input types including password, search, and textarea variants. Features smooth transitions, customizable styling, and comprehensive accessibility support.
import { Input } from 'rnc-theme';
<Input label="Email" placeholder="Enter your email" value={email} onChangeText={setEmail}/>
<Input label="Username" placeholder="Enter username" value={username} onChangeText={setUsername} required error={usernameError} helperText="Username must be at least 3 characters"/>
<Input label="Full Name" floatingLabel value={name} onChangeText={setName} variant="outline"/>
<Input label="Search" placeholder="Search products..." leftIcon={<SearchIcon size={20} />} rightIcon={<FilterIcon size={20} />} onRightIconPress={handleFilterPress}/>
Prop | Type | Default | Description |
---|---|---|---|
label | string | - | Input field label |
placeholder | string | - | Placeholder text |
value | string | - | Controlled input value |
defaultValue | string | - | Default uncontrolled value |
onChangeText | (text: string) => void | - | Text change handler |
variant | ComponentVariant | 'default' | Visual style variant |
size | ComponentSize | 'md' | Input size (sm, md, lg) |
state | ComponentState | 'default' | Validation state |
Prop | Type | Default | Description |
---|---|---|---|
borderRadius | keyof Theme['components']['borderRadius'] | 'md' | Border radius value |
inputStyle | TextStyle | - | Custom input text styles |
labelStyle | TextStyle | - | Custom label styles |
helperTextStyle | TextStyle | - | Custom helper text styles |
style | ViewStyle | - | Container style overrides |
Prop | Type | Default | Description |
---|---|---|---|
disabled | boolean | false | Disable input interactions |
readOnly | boolean | false | Make input read-only |
required | boolean | false | Mark field as required |
animated | boolean | true | Enable animations |
autoFocus | boolean | false | Auto-focus on mount |
floatingLabel | boolean | false | Use floating label style |
Prop | Type | Default | Description |
---|---|---|---|
error | string | - | Error message to display |
helperText | string | - | Helper text below input |
maxLength | number | - | Maximum character limit |
showCharacterCount | boolean | false | Show character counter |
Prop | Type | Default | Description |
---|---|---|---|
leftIcon | React.ReactNode | - | Icon on the left side |
rightIcon | React.ReactNode | - | Icon on the right side |
onRightIconPress | () => void | - | Right icon press handler |
Prop | Type | Default | Description |
---|---|---|---|
isPasswordInput | boolean | false | Enable password input mode |
isSearchInput | boolean | false | Enable search input mode |
isTextAreaInput | boolean | false | Enable textarea mode |
showPasswordIcon | React.ReactNode | - | Custom show password icon |
hidePasswordIcon | React.ReactNode | - | Custom hide password icon |
Prop | Type | Default | Description |
---|---|---|---|
multiline | boolean | false | Enable multiline input |
numberOfLines | number | 1 | Number of visible lines |
Variant | Description | Use Case |
---|---|---|
default | Standard input style | General text inputs |
primary | Primary brand styling | Important form fields |
secondary | Secondary styling | Supporting form fields |
outline | Outlined border style | Prominent form inputs |
filled | Filled background style | Dense form layouts |
ghost | Minimal styling | Subtle input fields |
success | Success state styling | Validated fields |
error | Error state styling | Invalid fields |
warning | Warning state styling | Fields needing attention |
info | Information styling | Informational inputs |
destructive | Destructive action styling | Dangerous operations |
State | Description | Visual Effect |
---|---|---|
default | Normal input state | Standard appearance |
focus | Focused input state | Enhanced border, colors |
active | Active interaction | Pressed appearance |
disabled | Disabled input | Reduced opacity |
loading | Loading state | Loading indicators |
error | Error validation | Red border, error text |
success | Success validation | Green border |
warning | Warning state | Orange border |
const BasicForm = () => { const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const [email, setEmail] = useState('');
return ( <VStack spacing="lg" padding="lg"> <Input label="First Name" placeholder="Enter your first name" value={firstName} onChangeText={setFirstName} required />
<Input label="Last Name" placeholder="Enter your last name" value={lastName} onChangeText={setLastName} required />
<Input label="Email Address" placeholder="Enter your email" value={email} onChangeText={setEmail} keyboardType="email-address" autoCapitalize="none" required state={emailError ? 'error' : 'default'} error={emailError} /> </VStack> );};
const SpecializedInputs = () => { const [password, setPassword] = useState(''); const [searchQuery, setSearchQuery] = useState(''); const [description, setDescription] = useState('');
return ( <VStack spacing="lg" padding="lg"> {/* Password Input */} <Input label="Password" placeholder="Enter your password" value={password} onChangeText={setPassword} isPasswordInput required helperText="Must be at least 8 characters" />
{/* Search Input */} <Input placeholder="Search products..." value={searchQuery} onChangeText={setSearchQuery} isSearchInput variant="outline" />
{/* Textarea Input */} <Input label="Description" placeholder="Enter a detailed description..." value={description} onChangeText={setDescription} isTextAreaInput numberOfLines={4} maxLength={500} showCharacterCount /> </VStack> );};
const FloatingLabelForm = () => { const [formData, setFormData] = useState({ name: '', email: '', phone: '', company: '' });
const updateField = (field: string) => (value: string) => { setFormData(prev => ({ ...prev, [field]: value })); };
return ( <VStack spacing="xl" padding="lg"> <Input label="Full Name" value={formData.name} onChangeText={updateField('name')} floatingLabel variant="outline" required />
<Input label="Email Address" value={formData.email} onChangeText={updateField('email')} floatingLabel variant="outline" keyboardType="email-address" required />
<Input label="Phone Number" value={formData.phone} onChangeText={updateField('phone')} floatingLabel variant="outline" keyboardType="phone-pad" />
<Input label="Company" value={formData.company} onChangeText={updateField('company')} floatingLabel variant="outline" /> </VStack> );};
const ValidationExample = () => { const [username, setUsername] = useState(''); const [email, setEmail] = useState(''); const [confirmPassword, setConfirmPassword] = useState('');
const validateUsername = (value: string) => { if (value.length < 3) return 'Username must be at least 3 characters'; if (!/^[a-zA-Z0-9_]+$/.test(value)) return 'Only letters, numbers, and underscores allowed'; return ''; };
const validateEmail = (value: string) => { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(value) ? '' : 'Please enter a valid email address'; };
const usernameError = validateUsername(username); const emailError = validateEmail(email);
return ( <VStack spacing="lg" padding="lg"> <Input label="Username" placeholder="Choose a username" value={username} onChangeText={setUsername} state={usernameError ? 'error' : username.length > 0 ? 'success' : 'default'} error={usernameError} helperText="3+ characters, letters, numbers, and underscores only" required />
<Input label="Email" placeholder="Enter your email" value={email} onChangeText={setEmail} keyboardType="email-address" state={emailError ? 'error' : email.length > 0 ? 'success' : 'default'} error={emailError} required />
<Input label="Confirm Password" placeholder="Confirm your password" value={confirmPassword} onChangeText={setConfirmPassword} isPasswordInput state={confirmPassword !== password && confirmPassword.length > 0 ? 'error' : 'default'} error={confirmPassword !== password && confirmPassword.length > 0 ? 'Passwords do not match' : ''} required /> </VStack> );};
const IconInputs = () => { const [location, setLocation] = useState(''); const [budget, setBudget] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const handleLocationDetect = () => { // Implement location detection console.log('Detecting location...'); };
const handleClearSearch = () => { setSearchTerm(''); };
return ( <VStack spacing="lg" padding="lg"> <Input label="Location" placeholder="Enter your location" value={location} onChangeText={setLocation} leftIcon={<MapPinIcon size={20} color="#666" />} rightIcon={<GpsIcon size={20} color="#0066CC" />} onRightIconPress={handleLocationDetect} />
<Input label="Budget Range" placeholder="Enter maximum budget" value={budget} onChangeText={setBudget} leftIcon={<DollarSignIcon size={20} color="#666" />} keyboardType="numeric" />
<Input placeholder="Search anything..." value={searchTerm} onChangeText={setSearchTerm} leftIcon={<SearchIcon size={20} color="#666" />} rightIcon={ searchTerm.length > 0 ? ( <XIcon size={20} color="#666" /> ) : ( <MicIcon size={20} color="#666" /> ) } onRightIconPress={searchTerm.length > 0 ? handleClearSearch : handleVoiceSearch} variant="filled" /> </VStack> );};
<VStack spacing="lg" align="stretch"> <Input size="sm" label="Small Input" placeholder="Small size input" variant="outline" />
<Input size="md" label="Medium Input" placeholder="Medium size input (default)" variant="outline" />
<Input size="lg" label="Large Input" placeholder="Large size input" variant="outline" /></VStack>
const ContactForm = () => { const [formData, setFormData] = useState({ name: '', email: '', phone: '', subject: '', message: '' }); const [errors, setErrors] = useState({}); const [isSubmitting, setIsSubmitting] = useState(false);
const updateField = (field: string) => (value: string) => { setFormData(prev => ({ ...prev, [field]: value })); // Clear error when user starts typing if (errors[field]) { setErrors(prev => ({ ...prev, [field]: '' })); } };
const validateForm = () => { const newErrors = {};
if (!formData.name.trim()) newErrors.name = 'Name is required'; if (!formData.email.trim()) newErrors.email = 'Email is required'; if (!formData.message.trim()) newErrors.message = 'Message is required';
setErrors(newErrors); return Object.keys(newErrors).length === 0; };
const handleSubmit = async () => { if (!validateForm()) return;
setIsSubmitting(true); try { await submitContactForm(formData); // Reset form or show success } finally { setIsSubmitting(false); } };
return ( <VStack spacing="lg" padding="lg"> <Text style={styles.formTitle}>Contact Us</Text>
<Input label="Full Name" placeholder="Enter your full name" value={formData.name} onChangeText={updateField('name')} required error={errors.name} leftIcon={<UserIcon size={20} color="#666" />} variant="outline" />
<Input label="Email Address" placeholder="Enter your email" value={formData.email} onChangeText={updateField('email')} keyboardType="email-address" autoCapitalize="none" required error={errors.email} leftIcon={<MailIcon size={20} color="#666" />} variant="outline" />
<Input label="Phone Number" placeholder="Enter your phone number" value={formData.phone} onChangeText={updateField('phone')} keyboardType="phone-pad" leftIcon={<PhoneIcon size={20} color="#666" />} variant="outline" />
<Input label="Subject" placeholder="What is this regarding?" value={formData.subject} onChangeText={updateField('subject')} variant="outline" />
<Input label="Message" placeholder="Enter your message here..." value={formData.message} onChangeText={updateField('message')} isTextAreaInput numberOfLines={5} maxLength={1000} showCharacterCount required error={errors.message} variant="outline" />
<Button onPress={handleSubmit} disabled={isSubmitting} loading={isSubmitting} variant="primary" size="lg" fullWidth > <ButtonText>Send Message</ButtonText> </Button> </VStack> );};
const useFormValidation = (rules) => { const [errors, setErrors] = useState({}); const [touched, setTouched] = useState({});
const validate = (field, value) => { const rule = rules[field]; if (!rule) return '';
if (rule.required && !value.trim()) { return rule.requiredMessage || `${field} is required`; }
if (rule.minLength && value.length < rule.minLength) { return `${field} must be at least ${rule.minLength} characters`; }
if (rule.pattern && !rule.pattern.test(value)) { return rule.patternMessage || `${field} format is invalid`; }
return ''; };
const validateField = (field, value) => { const error = validate(field, value); setErrors(prev => ({ ...prev, [field]: error })); return error === ''; };
const markTouched = (field) => { setTouched(prev => ({ ...prev, [field]: true })); };
return { errors, touched, validateField, markTouched, isValid: Object.values(errors).every(error => !error) };};
// Usageconst AdvancedForm = () => { const validationRules = { email: { required: true, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, patternMessage: 'Please enter a valid email' }, password: { required: true, minLength: 8 } };
const { errors, touched, validateField, markTouched, isValid } = useFormValidation(validationRules);
const [email, setEmail] = useState(''); const [password, setPassword] = useState('');
return ( <VStack spacing="lg"> <Input label="Email" value={email} onChangeText={(value) => { setEmail(value); if (touched.email) validateField('email', value); }} onBlur={() => { markTouched('email'); validateField('email', email); }} error={touched.email ? errors.email : ''} state={touched.email && errors.email ? 'error' : 'default'} />
<Input label="Password" value={password} onChangeText={(value) => { setPassword(value); if (touched.password) validateField('password', value); }} onBlur={() => { markTouched('password'); validateField('password', password); }} isPasswordInput error={touched.password ? errors.password : ''} state={touched.password && errors.password ? 'error' : 'default'} /> </VStack> );};
const DynamicInputList = () => { const [items, setItems] = useState([{ id: 1, value: '' }]);
const addItem = () => { const newId = Math.max(...items.map(item => item.id)) + 1; setItems(prev => [...prev, { id: newId, value: '' }]); };
const removeItem = (id) => { if (items.length > 1) { setItems(prev => prev.filter(item => item.id !== id)); } };
const updateItem = (id, value) => { setItems(prev => prev.map(item => item.id === id ? { ...item, value } : item )); };
return ( <VStack spacing="md" padding="lg"> <Text style={styles.sectionTitle}>Skills</Text>
{items.map((item, index) => ( <HStack key={item.id} spacing="sm" align="center"> <Input placeholder={`Skill ${index + 1}`} value={item.value} onChangeText={(value) => updateItem(item.id, value)} style={{ flex: 1 }} variant="outline" />
<Button variant="ghost" size="sm" onPress={() => removeItem(item.id)} disabled={items.length === 1} > <ButtonIcon icon={<XIcon size={20} />} /> </Button> </HStack> ))}
<Button variant="outline" onPress={addItem} size="sm" alignSelf="flex-start" > <ButtonIcon icon={<PlusIcon size={16} />} position="left" /> <ButtonText>Add Skill</ButtonText> </Button> </VStack> );};
The Input component features smooth animations that enhance user experience:
<Input label="Animated Input" animated={true} variant="primary" placeholder="Focus to see animation"/>
<Input label="Floating Label" floatingLabel={true} animated={true} variant="outline"/>
<Input label="Validation Input" error="This field has an error" animated={true} state="error"/>
Form Design
Performance
React.memo
for input components in listsValidation UX