@cermuel/smart-form
v1.0.0
Published
A type-safe, flexible React form component with built-in validation and Tailwind styling
Maintainers
Readme
SmartForm
A type-safe, flexible React form component with built-in validation and beautiful Tailwind styling. Build forms quickly with automatic validation, error handling, and customizable styling.
Features
✨ Type-Safe - Full TypeScript support with intelligent type inference
🎨 Styled with Tailwind - Beautiful, modern design out of the box
✅ Built-in Validation - Required fields, min/max values, regex patterns
🎯 Flexible - Use default UI or build completely custom forms
📦 Zero Config - Just import and use
🔒 Password Toggle - Built-in show/hide functionality
📝 Multiple Field Types - Text, email, number, password, textarea, checkbox, select, date, time
🎭 Custom Rendering - Full control over form layout and styling
Installation
npm install smart-formQuick Start
1. Import the CSS (Required!)
In your main entry file (App.tsx, main.tsx, or _app.tsx):
import 'smart-form/dist/styles.css';2. Use the Form
import { SmartForm } from 'smart-form';
function MyForm() {
return (
<SmartForm
schema={{
email: {
type: 'email',
label: 'Email Address',
required: true,
},
password: {
type: 'password',
label: 'Password',
required: true,
min: 8,
},
}}
onSubmit={(values) => {
console.log(values); // { email: "...", password: "..." }
}}
buttonTitle="Sign In"
/>
);
}Field Types
Text Input
email: {
type: 'email', // or 'string', 'number'
label: 'Email',
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
icon: <MailIcon />,
}Password
password: {
type: 'password',
label: 'Password',
required: true,
min: 8,
passwordToggler: (visible) => visible ? <EyeOffIcon /> : <EyeIcon />,
}Textarea
message: {
type: 'textarea',
label: 'Message',
required: true,
max: 500,
}Checkbox
terms: {
type: 'checkbox',
label: 'Terms',
checkboxLabel: 'I agree to the terms and conditions',
required: true,
}Select
country: {
type: 'select',
label: 'Country',
required: true,
placeholder: 'Select a country',
options: [
{ value: 'us', label: 'United States', icon: <FlagIcon /> },
{ value: 'uk', label: 'United Kingdom' },
{ value: 'ca', label: 'Canada' },
],
}Date & Time
birthdate: {
type: 'date',
label: 'Date of Birth',
required: true,
}
appointmentTime: {
type: 'time',
label: 'Appointment Time',
required: true,
}Validation
Built-in Validators
schema={{
age: {
type: 'number',
label: 'Age',
required: true,
min: 18,
max: 100,
},
website: {
type: 'string',
label: 'Website',
pattern: /^https?:\/\/.+/,
},
}}Validation Rules
required: boolean- Field must have a valuemin: number- Minimum value for numbersmax: number- Maximum value for numberspattern: RegExp- Custom regex validation
Default Values
schema={{
username: {
type: 'string',
label: 'Username',
defaultValue: 'johndoe',
},
newsletter: {
type: 'checkbox',
checkboxLabel: 'Subscribe to newsletter',
defaultValue: true,
},
}}Styling
Using Default Styles
<SmartForm
schema={schema}
onSubmit={handleSubmit}
formClassname="max-w-md mx-auto"
className="mb-4"
labelClassname="font-bold"
inputClassname="shadow-sm"
buttonClassname="hover:bg-gray-800"
/>Custom Button
<SmartForm
schema={schema}
onSubmit={handleSubmit}
buttonTitle="Create Account"
buttonLoading={isLoading}
buttonDisabled={isDisabled}
loadingIndicator={<Spinner />}
/>Advanced Usage
Custom Form Layout
For complete control over your form's appearance:
<SmartForm schema={schema} onSubmit={handleSubmit}>
{({ values, setValue, errors }) => (
<div className="space-y-6">
<div className="grid grid-cols-2 gap-4">
<div>
<label>First Name</label>
<input
value={values.firstName}
onChange={(e) => setValue('firstName', e.target.value)}
className="custom-input"
/>
{errors.firstName && <span>{errors.firstName}</span>}
</div>
<div>
<label>Last Name</label>
<input
value={values.lastName}
onChange={(e) => setValue('lastName', e.target.value)}
className="custom-input"
/>
{errors.lastName && <span>{errors.lastName}</span>}
</div>
</div>
<button type="submit">Submit</button>
</div>
)}
</SmartForm>Accessing Form Values
{({ values, setValue, errors }) => (
<>
<input
value={values.email}
onChange={(e) => setValue('email', e.target.value)}
/>
{/* Conditional rendering based on values */}
{values.country === 'us' && (
<input
value={values.state}
onChange={(e) => setValue('state', e.target.value)}
/>
)}
{/* Display errors */}
{errors.email && (
<span className="text-red-500">{errors.email}</span>
)}
</>
)}Complete Examples
Login Form
import { SmartForm } from 'smart-form';
import 'smart-form/dist/styles.css';
function LoginForm() {
const handleLogin = async (values) => {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(values),
});
// Handle response
};
return (
<SmartForm
schema={{
email: {
type: 'email',
label: 'Email',
required: true,
},
password: {
type: 'password',
label: 'Password',
required: true,
min: 8,
},
remember: {
type: 'checkbox',
checkboxLabel: 'Remember me',
},
}}
onSubmit={handleLogin}
buttonTitle="Sign In"
formClassname="max-w-md mx-auto p-6 bg-white rounded-lg shadow"
/>
);
}Registration Form
import { SmartForm } from 'smart-form';
import 'smart-form/dist/styles.css';
function RegistrationForm() {
return (
<SmartForm
schema={{
fullName: {
type: 'string',
label: 'Full Name',
required: true,
},
email: {
type: 'email',
label: 'Email',
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
},
password: {
type: 'password',
label: 'Password',
required: true,
min: 8,
},
age: {
type: 'number',
label: 'Age',
required: true,
min: 18,
},
country: {
type: 'select',
label: 'Country',
required: true,
placeholder: 'Select your country',
options: [
{ value: 'us', label: 'United States' },
{ value: 'uk', label: 'United Kingdom' },
{ value: 'ca', label: 'Canada' },
],
},
terms: {
type: 'checkbox',
label: 'Terms',
checkboxLabel: 'I agree to the terms and conditions',
required: true,
},
}}
onSubmit={(values) => {
console.log('Registration:', values);
}}
buttonTitle="Create Account"
formClassname="max-w-lg mx-auto p-8 bg-white rounded-xl shadow-lg"
/>
);
}Contact Form with Custom Layout
import { SmartForm } from 'smart-form';
import 'smart-form/dist/styles.css';
function ContactForm() {
return (
<SmartForm
schema={{
name: { type: 'string', label: 'Name', required: true },
email: { type: 'email', label: 'Email', required: true },
subject: { type: 'string', label: 'Subject', required: true },
message: { type: 'textarea', label: 'Message', required: true, max: 500 },
}}
onSubmit={(values) => console.log(values)}
>
{({ values, setValue, errors }) => (
<div className="max-w-2xl mx-auto p-6 bg-white rounded-lg shadow">
<h2 className="text-2xl font-bold mb-6">Contact Us</h2>
<div className="grid grid-cols-2 gap-4 mb-4">
<div>
<label className="block text-sm font-medium mb-1">Name</label>
<input
value={values.name}
onChange={(e) => setValue('name', e.target.value)}
className="w-full px-3 py-2 border rounded-lg"
/>
{errors.name && <span className="text-red-500 text-xs">{errors.name}</span>}
</div>
<div>
<label className="block text-sm font-medium mb-1">Email</label>
<input
value={values.email}
onChange={(e) => setValue('email', e.target.value)}
className="w-full px-3 py-2 border rounded-lg"
/>
{errors.email && <span className="text-red-500 text-xs">{errors.email}</span>}
</div>
</div>
<div className="mb-4">
<label className="block text-sm font-medium mb-1">Subject</label>
<input
value={values.subject}
onChange={(e) => setValue('subject', e.target.value)}
className="w-full px-3 py-2 border rounded-lg"
/>
{errors.subject && <span className="text-red-500 text-xs">{errors.subject}</span>}
</div>
<div className="mb-6">
<label className="block text-sm font-medium mb-1">Message</label>
<textarea
value={values.message}
onChange={(e) => setValue('message', e.target.value)}
rows={5}
className="w-full px-3 py-2 border rounded-lg"
/>
{errors.message && <span className="text-red-500 text-xs">{errors.message}</span>}
</div>
<button
type="submit"
className="w-full bg-blue-600 text-white py-3 rounded-lg hover:bg-blue-700"
>
Send Message
</button>
</div>
)}
</SmartForm>
);
}API Reference
FormProps
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| schema | FormSchema | ✅ | Form field definitions |
| onSubmit | (values) => void | ✅ | Submit handler |
| children | (params) => ReactNode | ❌ | Custom render function |
| formClassname | string | ❌ | Form element class |
| className | string | ❌ | Field wrapper class |
| labelClassname | string | ❌ | Label class |
| inputClassname | string | ❌ | Input wrapper class |
| buttonClassname | string | ❌ | Button class |
| buttonTitle | string | ❌ | Button text (default: "Submit") |
| buttonLoading | boolean | ❌ | Loading state |
| buttonDisabled | boolean | ❌ | Disabled state |
| loadingIndicator | ReactNode | ❌ | Custom loading indicator |
Field Schema
type FieldSchema = {
type?: 'string' | 'email' | 'number' | 'password' | 'textarea' |
'checkbox' | 'select' | 'date' | 'time';
label?: string;
required?: boolean;
min?: number;
max?: number;
pattern?: RegExp;
defaultValue?: string | number | boolean;
disabled?: boolean;
icon?: ReactNode;
// Password specific
passwordToggler?: (visible: boolean) => ReactNode;
// Checkbox specific
checkboxLabel?: string;
// Select specific
options?: Array<{ value: string | number; label: string; icon?: ReactNode }>;
placeholder?: string;
};TypeScript Support
SmartForm is fully typed. Your form values will be automatically inferred:
const schema = {
email: { type: 'email' as const, required: true },
age: { type: 'number' as const },
terms: { type: 'checkbox' as const },
} as const;
<SmartForm
schema={schema}
onSubmit={(values) => {
// values is typed as:
// { email: string; age: string; terms: boolean }
console.log(values.email); // ✅ TypeScript knows this is a string
}}
/>Browser Support
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
License
MIT © Samuel Ngene
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Support
- 📧 Email: [email protected]
- 🐛 Issues: GitHub Issues
- 📖 Docs: GitHub
- 👨🏾💻 Author: GitHub
Made with ❤️ by Samuel Ngene
