react-smart-fields
v2.4.0
Published
A flexible and highly customizable dynamic form builder for React with validation, conditional fields, theming, and headless rendering.
Maintainers
Readme
React Smart Fields
react-smart-fields is a powerful dynamic form renderer for React with:
- configurable field schemas
- built-in + custom validation
- conditional visibility/disable/required rules
- async options for selects
- nested field names (
user.addresses[0].city) - theme presets + headless mode + full render slots
Installation
npm install react-smart-fieldsor
yarn add react-smart-fieldsQuick Start
// 1. Import the default styles once (e.g. in your root layout or App.tsx)
import 'react-smart-fields/styles.css';
import { DynamicFields, FieldConfig } from "react-smart-fields";
const fields: FieldConfig[] = [
{ name: "name", label: "Name", type: "text", required: true },
{
name: "email",
label: "Email",
type: "email",
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
patternMessage: "Please enter a valid email",
},
{
name: "country",
label: "Country",
type: "select",
placeholder: "Select country",
options: [
{ label: "India", value: "in" },
{ label: "United States", value: "us" },
],
},
{
name: "newsletter",
label: "Subscribe to newsletter",
type: "checkbox",
defaultValue: true,
},
];
export default function App() {
return (
<DynamicFields
fields={fields}
title="Profile"
description="Update your profile details"
theme="default"
size="md"
onChange={(values, meta) => {
console.log(values, meta?.errors);
}}
/>
);
}Major Capabilities
- Validation engine:
required,min/max,minLength/maxLength,pattern, andvalidate - Conditional behavior:
showWhen,disableWhen,requiredWhen - Nested paths: dot/bracket field names are supported
- Async select options:
loadOptionswithloadOptionsOn: "mount" | "change" - Theme presets:
default,minimal,filled,underline - Headless mode: set
headlessto fully control UI - Render slots:
renderField,renderControl,renderLabel,renderDescription,renderError - State styling hooks:
stateClassNamesforinvalid,disabled,readonly,dirty,touched
Component Props
| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| fields | FieldConfig[] | ✅ | Field schema array |
| onChange | (values, meta?) => void | ✅ | Fires on every value/meta update |
| value | Record<string, any> | ❌ | Controlled mode values |
| defaultValues | Record<string, any> | ❌ | Initial values (uncontrolled mode) |
| resolver | (values) => errors \| Promise<errors> | ❌ | External validation resolver |
| validateMode | "change" \| "blur" | ❌ | Validation trigger mode |
| showErrorWhen | "always" \| "touched" \| "dirty" | ❌ | Error visibility strategy |
| headless | boolean | ❌ | Disables built-in styling |
| theme | "default" \| "minimal" \| "filled" \| "underline" | ❌ | Built-in visual preset |
| size | "sm" \| "md" \| "lg" | ❌ | Input/label sizing |
| ui | UiClassMap | ❌ | Global slot class overrides |
| stateClassNames | StateClassNames | ❌ | State class hooks |
| renderField | (ctx) => ReactNode | ❌ | Full custom field renderer |
| renderControl | (ctx) => ReactNode | ❌ | Custom control renderer |
| renderLabel | (ctx) => ReactNode | ❌ | Custom label renderer |
| renderDescription | (ctx) => ReactNode | ❌ | Custom helper text renderer |
| renderError | (ctx) => ReactNode | ❌ | Custom error renderer |
| title | string | ❌ | Form heading |
| description | string | ❌ | Form subtitle |
Legacy class props are still supported for backward compatibility:
className, inputClassName, labelClassName, mainFieldClassName, fieldClassName, errorClassName, selectClassName, optionClassName, checkboxClassName, radioClassName.
FieldConfig API
type FieldConfig = {
name: string;
label?: string;
type:
| "text"
| "number"
| "select"
| "radio"
| "checkbox"
| "textarea"
| "email"
| "password"
| "tel"
| "url"
| "date";
// values/options
defaultValue?: any;
placeholder?: string;
options?: { label: string; value: string | number | boolean | null | undefined; disabled?: boolean }[];
loadOptions?: (ctx: { values: Record<string, any>; field: FieldConfig }) => Promise<FieldOption[]>;
loadOptionsOn?: "mount" | "change";
// behavior
disabled?: boolean;
readOnly?: boolean;
showWhen?: (values: Record<string, any>) => boolean;
disableWhen?: (values: Record<string, any>) => boolean;
required?: boolean;
requiredWhen?: (values: Record<string, any>) => boolean;
requiredMessage?: string;
// validation
min?: number;
max?: number;
minLength?: number;
maxLength?: number;
pattern?: RegExp;
patternMessage?: string;
validate?: (value: any, values: Record<string, any>) => string | undefined | null;
// value transforms
transformIn?: (storedValue: any, values: Record<string, any>) => any;
transformOut?: (rawValue: any, values: Record<string, any>) => any;
// styling/rendering
className?: string;
inputClassName?: string;
labelClassName?: string;
errorClassName?: string;
ui?: UiClassMap;
renderControl?: (ctx: RenderControlContext) => ReactNode;
};Customization Examples
1) Conditional + Nested Fields
const fields: FieldConfig[] = [
{ name: "accountType", label: "Account Type", type: "select", options: [
{ label: "Individual", value: "individual" },
{ label: "Business", value: "business" },
]},
{
name: "company.name",
label: "Company Name",
type: "text",
showWhen: (values) => values.accountType === "business",
requiredWhen: (values) => values.accountType === "business",
},
{
name: "addresses[0].city",
label: "Primary City",
type: "text",
required: true,
},
];2) Async Select Options
{
name: "state",
label: "State",
type: "select",
loadOptionsOn: "change",
loadOptions: async ({ values }) => {
if (!values.country) return [];
const response = await fetch(`/api/states?country=${values.country}`);
const data = await response.json();
return data.map((item: any) => ({ label: item.name, value: item.code }));
},
}3) Headless + Slots
<DynamicFields
fields={fields}
headless
renderField={({ field, labelNode, controlNode, errorNode, helpNode }) => (
<div key={field.name} className="mb-6">
{labelNode}
<div className="mt-2">{controlNode}</div>
<div className="mt-1">{helpNode}</div>
<div className="mt-1">{errorNode}</div>
</div>
)}
onChange={(values) => console.log(values)}
/>4) Resolver Validation
<DynamicFields
fields={fields}
resolver={(values) => {
const errors: Record<string, string> = {};
if (values.password !== values.confirmPassword) {
errors.confirmPassword = "Passwords do not match";
}
return errors;
}}
onChange={(values, meta) => {
console.log(meta?.errors);
}}
/>Type Exports
import type {
FieldConfig,
FieldOption,
FieldType,
DynamicFieldsProps,
UiClassMap,
StateClassNames,
ChangeMeta,
RenderControlContext,
RenderFieldContext,
} from "react-smart-fields";Author
Name: Pratik Panchal
GitHub: @Pratikpanchal25
License
MIT — Free to use, modify and distribute.
Contributing
Pull requests are welcome! If you find bugs, feel free to open an issue.
