In modern web applications, forms are a critical component, but managing various types of form inputs with consistent styling and functionality can be challenging. To address this, we’ve developed a dynamic form component in React using TailwindCSS that is flexible, handles a variety of input types, and offers a seamless user experience. This component allows developers to pass a JSON schema, which automatically renders form elements with a consistent UI, making it adaptable for any form requirement.
The Dynamic Form Component
Here’s an overview of the form component we’ve built:
- Handles Multiple Input Types: The component supports text, number, email, password, radio buttons, checkboxes, and select dropdowns.
- Automatic Styling: Using TailwindCSS, we’ve ensured that each element has consistent styling, spacing, and a modern, polished look.
- Customizable Theme Color: Developers can set a
themeColor
prop to control the primary color throughout the form, affecting elements like buttons, focus rings, and icons. - Password Toggle: For password fields, an eye icon appears, allowing users to toggle between hiding and showing the password for convenience.
- Centered and Spaced Layout: The form is centrally aligned on the page with generous spacing between elements for a clean and modern UI.
Let’s dive into the code to see how this component is implemented:
import React, { useState, useMemo } from 'react';
const FormComponent = ({ fields, themeColor = 'blue' }) => {
const [formData, setFormData] = useState({});
const [showPassword, setShowPassword] = useState({});
const handleChange = (e, name) => {
setFormData((prevData) => ({
...prevData,
[name]: e.target.value,
}));
};
const togglePasswordVisibility = (name) => {
setShowPassword((prev) => ({
...prev,
[name]: !prev[name],
}));
};
const themeClasses = useMemo(() => ({
input:
`w-full px-4 py-3 border rounded focus:outline-none focus:ring-2 focus:ring-${themeColor}-500`,
label: 'block text-gray-700 font-semibold mb-2',
button: `w-full px-4 py-2 bg-${themeColor}-500 text-white rounded hover:bg-${themeColor}-600 focus:outline-none focus:ring-2 focus:ring-${themeColor}-500`,
checkboxRadio: `form-${themeColor}`,
icon: `text-${themeColor}-600 cursor-pointer`,
}), [themeColor]);
const renderInput = (field) => {
const { type, label, name, options, placeholder } = field;
switch (type) {
case 'text':
case 'number':
case 'email':
case 'date':
return (
<div className="mb-6">
<label className={themeClasses.label}>{label}</label>
<input
type={type}
name={name}
placeholder={placeholder}
value={formData[name] || ''}
onChange={(e) => handleChange(e, name)}
className={themeClasses.input}
/>
</div>
);
case 'password':
return (
<div className="mb-6">
<label className={themeClasses.label}>{label}</label>
<div className="relative">
<input
type={showPassword[name] ? 'text' : 'password'}
name={name}
placeholder={placeholder}
value={formData[name] || ''}
onChange={(e) => handleChange(e, name)}
className={themeClasses.input}
/>
<span
onClick={() => togglePasswordVisibility(name)}
className={`absolute inset-y-0 right-0 flex items-center pr-3 ${themeClasses.icon}`}
>
{showPassword[name] ? '👁️' : '👁️🗨️'}
</span>
</div>
</div>
);
case 'radio':
return (
<div className="mb-6">
<label className={themeClasses.label}>{label}</label>
<div className="flex items-center space-x-4">
{options.map((option) => (
<label key={option.value} className="flex items-center space-x-2">
<input
type="radio"
name={name}
value={option.value}
checked={formData[name] === option.value}
onChange={(e) => handleChange(e, name)}
className={themeClasses.checkboxRadio}
/>
<span>{option.label}</span>
</label>
))}
</div>
</div>
);
case 'checkbox':
return (
<div className="mb-6">
<label className={themeClasses.label}>{label}</label>
<div className="flex items-center space-x-4">
{options.map((option) => (
<label key={option.value} className="flex items-center space-x-2">
<input
type="checkbox"
name={name}
value={option.value}
checked={formData[name]?.includes(option.value)}
onChange={(e) =>
setFormData((prevData) => ({
...prevData,
[name]: formData[name]
? formData[name].includes(option.value)
? formData[name].filter((val) => val !== option.value)
: [...formData[name], option.value]
: [option.value],
}))
}
className={themeClasses.checkboxRadio}
/>
<span>{option.label}</span>
</label>
))}
</div>
</div>
);
case 'select':
return (
<div className="mb-6">
<label className={themeClasses.label}>{label}</label>
<select
name={name}
value={formData[name] || ''}
onChange={(e) => handleChange(e, name)}
className={themeClasses.input}
>
<option value="">Select {label}</option>
{options.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
);
default:
return null;
}
};
return (
<div className="flex justify-center items-center min-h-screen bg-gray-100">
<form className="w-full max-w-lg mx-auto p-8 border rounded-lg shadow-md bg-white">
<h2 className="text-2xl font-bold text-center mb-6">Dynamic Form</h2>
{fields.map((field) => (
<div key={field.name}>{renderInput(field)}</div>
))}
<button type="submit" className={themeClasses.button}>
Submit
</button>
</form>
</div>
);
};
export default FormComponent;
How It Works
- Customizable UI with TailwindCSS: The form fields, button, and icons use TailwindCSS classes, creating a cohesive design. By passing a
themeColor
prop (e.g.,orange
orblue
), you can change the color of interactive elements across the entire form. - Responsive and Modern Design: Each field is neatly spaced, and the form is centered on the screen for a balanced layout, making it visually appealing and mobile-friendly.
- Dynamic Field Handling: The component dynamically renders input fields based on the passed JSON schema, making it versatile enough to handle different form layouts without additional coding.
- Password Visibility Toggle: For password fields, an eye icon appears, allowing users to toggle between showing and hiding their input, a common and user-friendly feature.
Example Usage
To use this form component, pass it a JSON array defining the fields. Here’s an example:
const formFields = [
{ type: 'text', label: 'Name', name: 'name', placeholder: 'Enter your name' },
{ type: 'email', label: 'Email', name: 'email', placeholder: 'Enter your email' },
{ type: 'password', label: 'Password', name: 'password', placeholder: 'Enter your password' },
{ type: 'radio', label: 'Gender', name: 'gender', options: [
{ label: 'Male', value: 'male' },
{ label: 'Female', value: 'female' },
]
},
{ type: 'checkbox', label: 'Interests', name: 'interests', options: [
{ label: 'Music', value: 'music' },
{ label: 'Movies', value: 'movies' },
{ label: 'Sports', value: 'sports' },
]
},
{ type: 'select', label: 'Country', name: 'country', options: [
{ label: 'United States', value: 'us' },
{ label: 'Canada', value: 'ca' },
]
},
];
function App() {
return (
<FormComponent fields={formFields} themeColor="orange" />
);
}
export default App;
Suggestions for Improvement
- Validation and Error Handling: Consider adding validation rules, such as required fields and email format checks, with error messages to guide users.
- Accessibility Enhancements: Ensure that labels, focus indicators, and keyboard navigation meet accessibility standards, making the form usable for everyone.
- Form Submission Handling: Add a submit handler function to process form data after submission, connecting it to a backend service if needed.
By using this dynamic form component, you save time, enhance maintainability, and offer a user-friendly, consistent experience across all forms in your application. Let us know if you have additional suggestions or ideas to make this component even better!