tac-form is a powerful, JSON Schema-based library for creating dynamic forms in React applications. It offers a flexible and intuitive way to generate, manage, and validate forms with minimal boilerplate.
- Key Features
- Installation
- Basic Usage
- Form Configuration
- Field Types
- Conditional Rendering
- Validation
- External Component Integration
- Context and Form Management
- Theming and Styling
- Advanced Examples
- API Reference
- Contributing
- License
- Dynamic form generation based on JSON configuration
- Support for multiple field types
- Conditional field rendering
- Form-level and field-level validation
- Integration with external UI libraries
- Dark mode support
- Multi-form management via React Context
-
Set up Tailwind CSS (if not already configured in your project)
-
Install the library:
npm install tac-form
- Copy the component to your working directory:
npx tac-form add
Here's a simple example of how to use tac-form:
import React from 'react';
import { DynamicForm, FormProvider } from 'tac-form';
const App = () => {
const formConfig = {
form: {
id: 'simpleForm',
submitText: 'Submit',
onSubmit: (data) => {
console.log('Form submitted:', data);
},
},
fields: [
{
name: 'email',
label: 'Email',
type: 'email',
},
{
name: 'password',
label: 'Password',
type: 'password',
},
],
};
return (
<FormProvider>
<DynamicForm id="simpleForm" config={formConfig} />
</FormProvider>
);
};
export default App;
The FormConfig
object is the core of tac-form. It defines the structure and behavior of your form:
interface FormConfig<T extends Record<string, unknown>> {
form: {
id: string;
submitText: ReactNode | string;
onSubmit: (data: T) => void;
};
fields: FieldInput<T>[];
}
tac-form supports various field types out of the box:
Field Type | Component | Description |
---|---|---|
text | TextInput | Standard text input |
textarea | TextareaInput | Multi-line text input |
number | NumberInput | Numeric input |
EmailInput | Email input | |
select | SelectInput | Dropdown selection |
radio | RadioInput | Radio button group |
checkbox | CheckboxInput | Checkbox or checkbox group |
phone | PhoneInput | Phone number input |
date | DateInput | Date picker |
readonly | ReadonlyInput | Display-only field |
Example of different field types:
const formConfig = {
form: {
id: 'multiFieldForm',
submitText: 'Submit',
onSubmit: (data) => console.log(data),
},
fields: [
{
name: 'fullName',
label: 'Full Name',
type: 'text',
},
{
name: 'bio',
label: 'Biography',
type: 'textarea',
},
{
name: 'age',
label: 'Age',
type: 'number',
},
{
name: 'gender',
label: 'Gender',
type: 'select',
options: [
{ label: 'Male', value: 'male' },
{ label: 'Female', value: 'female' },
{ label: 'Other', value: 'other' },
],
},
{
name: 'newsletter',
label: 'Subscribe to newsletter',
type: 'checkbox',
},
],
};
You can conditionally render fields based on the values of other fields:
const formConfig = {
// ... other config
fields: [
{
name: 'hasChildren',
label: 'Do you have children?',
type: 'radio',
options: [
{ label: 'Yes', value: 'yes' },
{ label: 'No', value: 'no' },
],
},
{
name: 'numberOfChildren',
label: 'Number of children',
type: 'number',
dependency: {
on: ['hasChildren'],
condition: (values) => values.hasChildren === 'yes',
},
},
],
};
tac-form integrates with Zod for schema validation:
import { z } from 'zod';
const schema = z.object({
email: z.string().email('Invalid email address'),
password: z.string().min(8, 'Password must be at least 8 characters'),
});
const FormWithValidation = () => {
const formConfig = {
// ... form config
};
return <DynamicForm id="validatedForm" config={formConfig} schema={schema} />;
};
Note: schema
is optional. If not provided, the form will not be validated. but you have to pass the schema
for all the fields. and if field is atleast add z.string().min(1)
to it as, field uses ''
as default value .
You can integrate external UI components:
- External library component or Custom component are also supported having
name
,value
andonChange
as required props.
import { TextField } from '@mui/material';
const formConfig = {
// ... other config
fields: [
{
name: 'customInput',
label: 'Custom Input',
type: 'text',
component: TextField,
},
],
};
The FormProvider
allows you to manage multiple forms:
import { useFormContext } from 'tac-form';
const ParentComponent = () => {
const handleClick = (id: string) => {
const value = getFormValue(id, 'textField')
console.log({ value })
}
return (
<DynamicForm id="myForm" config={formConfig} />
);
};
tac-form supports dark mode and custom styling:
const formConfig = {
// ... other config
fields: [
{
name: 'styledInput',
label: 'Styled Input',
type: 'text',
customClassName: {
container: 'my-custom-container',
input: 'my-custom-input',
label: 'my-custom-label',
},
styles: {
input: { borderColor: 'blue' },
},
},
],
};
const ThemedForm = () => {
return <DynamicForm id="themedForm" config={formConfig} darkMode={true} />;
};
type testFormConfig = {
textField: string,
readOnly: string,
textareaField: string,
numberField: number,
selectField: string,
radioField: string,
checkboxField: string[],
phoneField: string,
dateField: string,
}
const testFormConfig: FormConfig<testFormConfig> = {
form: {
id: 'test',
submitText: 'submit',
onSubmit: (data) => {
console.log(data)
}
},
fields: [
{
name: 'textField',
label: 'Text Field',
type: 'text',
placeholder: 'Enter text',
},
{
name: 'readOnly',
label: 'read only',
type: 'readonly',
placeholder: 'Enter text',
value: 'Sample text',
},
{
name: 'textareaField',
label: 'Textarea Field',
type: 'textarea',
placeholder: 'Enter detailed text',
value: 'Sample textarea content',
},
{
name: 'numberField',
label: 'Number Field',
type: 'number',
placeholder: 'Enter a number',
value: 12345,
},
{
name: 'selectField',
label: 'Select Field',
type: 'select',
options: [
{ label: 'Option 1', value: 'option1' },
{ label: 'Option 2', value: 'option2' },
{ label: 'Option 3', value: 'option3' }
],
placeholder: 'slec',
value: 'option2',
},
{
name: 'radioField',
label: 'Radio Field',
type: 'radio',
options: [
{ label: 'Radio 1', value: 'radio1' },
{ label: 'Radio 2', value: 'radio2' },
{ label: 'Radio 3', value: 'radio3' }
],
value: 'radio1',
},
{
name: 'checkboxField',
label: 'Checkbox Field',
type: 'checkbox',
options: [
{ label: 'Checkbox 1', value: 'checkbox1' },
{ label: 'Checkbox 2', value: 'checkbox2' },
{ label: 'Checkbox 3', value: 'checkbox3' }
],
value: ['checkbox3', 'checkbox1'],
},
{
name: 'phoneField',
label: 'Phone Number Field',
type: 'phone',
placeholder: 'Enter phone number',
value: '918340453292',
},
{
name: 'dateField',
label: 'Date Field',
type: 'date',
placeholder: 'Select date',
value: '2023-08-27',
},
]
}
const testFormSchema = z.object({
textField: z.string().min(8, { message: 'Minimum 8 characters required' }),
readOnly: z.string(),
textareaField: z.string(),
numberField: z.number(),
selectField: z.string(),
radioField: z.string(),
checkboxField: z.array(z.string()),
phoneField: z.string(),
dateField: z.string().or(z.array(z.date())),
})
const Home = () => {
const { forms, getFormValue, addForm } = useFormContext<testFormConfig>()
const handleClick = (id: string) => {
const value = getFormValue(id, 'textField')
console.log({ value })
}
return (
<>
<button onClick={() => handleClick('2')}> click me </button>
<div className='w-full dark h-[100vh] flex justify-center items-center' >
<div className='w-[80%] h-auto bg-white dark:bg-slate-600 rounded-lg px-10 py-4'>
<DynamicForm
id='2'
config={testFormConfig}
schema={testFormSchema}
/>
</div>
</div>
</>
)
}
export default Home
import React, { useState } from 'react';
import { DynamicForm, FormProvider } from 'tac-form';
const MultiStepForm = () => {
const [step, setStep] = useState(1);
const stepOneConfig = {
form: {
id: 'stepOne',
submitText: 'Next',
onSubmit: (data) => {
console.log('Step One Data:', data);
setStep(2);
},
},
fields: [
{ name: 'firstName', label: 'First Name', type: 'text' },
{ name: 'lastName', label: 'Last Name', type: 'text' },
],
};
const stepTwoConfig = {
form: {
id: 'stepTwo',
submitText: 'Submit',
onSubmit: (data) => {
console.log('Step Two Data:', data);
// Handle final submission
},
},
fields: [
{ name: 'email', label: 'Email', type: 'email' },
{ name: 'phone', label: 'Phone', type: 'phone' },
],
};
return (
<FormProvider>
{step === 1 && <DynamicForm id="stepOne" config={stepOneConfig} />}
{step === 2 && <DynamicForm id="stepTwo" config={stepTwoConfig} />}
</FormProvider>
);
};
export default MultiStepForm;
import { z } from 'zod';
import { DynamicForm } from 'tac-form';
const complexSchema = z.object({
username: z.string().min(3, 'Username must be at least 3 characters'),
email: z.string().email('Invalid email address'),
password: z.string().min(8, 'Password must be at least 8 characters'),
confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ['confirmPassword'],
});
const ComplexForm = () => {
const formConfig = {
form: {
id: 'complexForm',
submitText: 'Register',
onSubmit: (data) => console.log('Form submitted:', data),
},
fields: [
{ name: 'username', label: 'Username', type: 'text' },
{ name: 'email', label: 'Email', type: 'email' },
{ name: 'password', label: 'Password', type: 'password' },
{ name: 'confirmPassword', label: 'Confirm Password', type: 'password' },
],
};
return <DynamicForm id="complexForm" config={formConfig} schema={complexSchema} />;
};
export default ComplexForm;
- Otp Form
- Native Multi Step Form
- File Upload Form
- Image Crop Form
- Signature Form
tac-form is released under the MIT License.