
Security News
Feross on TBPN: How North Korea Hijacked Axios
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.
react-form-dto
Advanced tools
Schema-first, DTO-driven form framework for React and Material UI (MUI v7)
React Form DTO is a high-level form framework for building complex, dynamic, and enterprise-scale forms using declarative JSON or TypeScript DTOs—rather than verbose, repetitive JSX.
It is designed for schema-driven UIs, backend-configured workflows, admin panels, and internal tools where forms must be configurable, scalable, and predictable.
Most form libraries solve state management.
React Form DTO solves form architecture.
It operates at a higher abstraction level where layout, validation, rendering, and behavior are defined in a single schema.
| Feature | React Form DTO | React Hook Form | Formik |
|---|---|---|---|
| Schema / DTO driven | ✅ Native | ❌ Manual | ❌ Manual |
| MUI-first | ✅ Yes | ⚠️ Partial | ⚠️ Partial |
| Imperative API | ✅ First-class | ⚠️ Limited | ⚠️ Limited |
| Large dynamic forms | ✅ Excellent | ⚠️ Medium | ❌ Poor |
| Boilerplate | ✅ Minimal | ❌ High | ❌ High |
Note: React Form DTO is not a replacement for React Hook Form.
It is a higher-level abstraction for schema-driven UI generation.
React Form DTO excels when forms are data, not components.
npm install react-form-dto
# or
yarn add react-form-dto
# or
pnpm add react-form-dto
See CHANGELOG.md for detailed version history.
All structure, layout, validation, and behavior live in a single schema.
The UI is derived entirely from the DTO and internal state.
Refs enable workflows that declarative-only approaches struggle with.
Field logic is decoupled from presentation, enabling customization.
import { FormBuilder, type FormBuilderHandle } from 'react-form-dto';
import { useRef } from 'react';
import { myFormDTO } from './myFormDTO';
const formRef = useRef<FormBuilderHandle>(null);
const handleSubmit = (e) => {
e.preventDefault();
if (!formRef.current) return;
const values = formRef.current.getValues();
const errors = formRef.current.validateAll();
formRef.current.handleChange?.("firstName", "NewName");
const firstNameErrors = formRef.current.validateField("firstName");
const allErrors = formRef.current.getErrors();
};
return (
<form onSubmit={handleSubmit}>
<FormBuilder ref={formRef} dto={myFormDTO} />
<button type="submit">Submit</button>
</form>
);
useFormBuilderController is a hook that wraps FormBuilder and gives you:
Form component (no manual mapping of sections/fields)It combines the convenience of FormBuilder with the programmatic control of useFormBuilder.
import { useFormBuilderController } from 'react-form-dto';
import { myFormDTO } from './myFormDTO';
function MyFormWithController() {
const formController = useFormBuilderController({
dto: myFormDTO,
locale: 'en',
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const errors = formController.validateAll();
const hasErrors = Object.values(errors).some(
(err) => err && err.length > 0,
);
if (hasErrors) {
console.log('Validation errors:', errors);
return;
}
console.log('Form submitted:', formController.getValues());
};
return (
<form onSubmit={handleSubmit}>
{/* Auto-renders the full form from the DTO */}
<formController.Form />
<button type="submit">Submit</button>
</form>
);
}
const controller = useFormBuilderController({
dto: myFormDTO,
locale: 'en',
renderers: {
// optional: override field types
text: MyTextField,
},
handleChangeCallback: (id, value) => {
// optional: side effects on every field change
console.log(id, value);
},
});
// Reading and validating
controller.getValues(); // Record<string, any>
controller.getErrors(); // Record<string, string | null>
controller.validateAll(); // Record<string, string[] | null>
controller.validateField('id'); // string[]
// Programmatic updates
controller.handleChange('firstName', 'Jane');
import { useFormBuilderController } from 'react-form-dto';
import { myFormDTO } from './myFormDTO';
import { TextInput, SelectInput } from './customFields';
const renderers = {
text: TextInput,
select: SelectInput,
};
function AdvancedForm() {
const formController = useFormBuilderController({
dto: myFormDTO,
locale: 'en',
renderers,
handleChangeCallback: (id, value) => {
// sync with external store / analytics, etc.
console.log('Changed:', id, value);
},
});
const prefill = () => {
formController.handleChange('firstName', 'John');
formController.handleChange('lastName', 'Doe');
};
return (
<>
<button type="button" onClick={prefill}>
Prefill
</button>
<form
onSubmit={(e) => {
e.preventDefault();
const errors = formController.validateAll();
const hasErrors = Object.values(errors).some(
(err) => err && err.length > 0,
);
if (!hasErrors) {
console.log('Values:', formController.getValues());
}
}}
>
<formController.Form />
<button type="submit">Submit</button>
</form>
</>
);
}
For the full hook reference, see
Docs → Hooks →useFormBuilderController:
docs/api/hooks/useFormBuilderController.md

The form in the image above is generated from this DTO.
const profileForm: FormDTO = {
title: "User Profile",
description: "Fill out your personal information",
layout: { cols: 12, gap: "1rem" }, // global form layout
sections: [
{
id: "personal",
heading: "Personal Information",
description: "Basic details about you",
layout: { cols: 12, gap: "1rem" }, // section layout
fields: [
{
id: "title",
type: "select",
label: "Title",
placeholder: "Select your title",
options: ["Mr", "Ms", "Dr", "Prof"],
layout: { cols: 4 },
},
{
id: "firstName",
type: "text",
label: "First Name",
layout: { cols: 4 },
},
{
id: "lastName",
type: "text",
label: "Last Name",
layout: { cols: 4 },
},
{
id: "age",
type: "number",
label: "Age",
layout: { cols: 6 },
},
{
id: "dob",
type: "date",
label: "Date of Birth",
layout: { cols: 6 },
},
{
id: "skills",
type: "multi-autocomplete",
label: "Skills",
placeholder: "Select your skills",
options: [
"React",
"TypeScript",
"Node.js",
"GraphQL",
"Docker",
],
layout: { cols: 12 },
validations: {
required: "Select at least one skill",
validate: (val: string[]) =>
val && val.length < 2
? "Pick at least 2 skills"
: null,
},
},
],
},
{
id: "contact",
heading: "Contact Information",
layout: { cols: 12 },
fields: [
{
id: "email",
type: "email",
label: "Email",
validations: {
required: "Email is required",
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
},
},
{ id: "phone", type: "text", label: "Phone Number" },
{
id: "country",
type: "autocomplete",
label: "Country",
placeholder: "Select a country",
options: ["Pakistan", "India", "USA", "UK", "Germany"],
layout: { cols: 6 },
},
],
},
],
};
textnumberdateemailpasswordtextareaselectautocompletemulti-autocompleteradiocheckboxvisibleWhenReact Form DTO supports dynamic field visibility based on the values of other fields in the form. This is achieved through the visibleWhen property, which allows you to define simple conditions or complex logical expressions.
Show a field only when another field has a specific value:
{
id: "partnerName",
type: "text",
label: "Partner Name",
visibleWhen: {
field: "maritalStatus",
equals: "married"
}
}
For detail documentation, please visit Docs
React Form DTO has built-in support for multi-language forms through I18String and I18nOption types.
Any text property (label, placeholder, title, description, validation messages) can be either a plain string or a locale map:
// Simple string (single language)
{
label: "First Name"
}
// Multi-language support
{
label: {
en: "First Name",
fr: "Prénom",
es: "Nombre",
de: "Vorname"
}
}
For select, autocomplete, and multi-autocomplete fields, you can use I18nOption objects to provide translatable labels while maintaining stable values:
{
id: "country",
type: "select",
label: { en: "Country", fr: "Pays" },
options: [
{
value: "us",
label: { en: "United States", fr: "États-Unis" }
},
{
value: "fr",
label: { en: "France", fr: "France" }
},
{
value: "de",
label: { en: "Germany", fr: "Allemagne" }
}
]
}
Backward Compatibility: Simple string arrays still work:
options: ["USA", "France", "Germany"] // Still supported
Validation messages also support I18n:
validations: {
required: {
en: "This field is required",
fr: "Ce champ est obligatoire",
es: "Este campo es obligatorio"
},
validate: (value) => value.length < 2
? {
en: "Minimum 2 characters",
fr: "Minimum 2 caractères"
}
: null
}
const multilingualForm: FormDTO = {
title: {
en: "User Registration",
fr: "Inscription de l'utilisateur",
es: "Registro de Usuario"
},
description: {
en: "Please fill in your details",
fr: "Veuillez remplir vos coordonnées",
es: "Por favor complete sus datos"
},
sections: [
{
id: "personal",
heading: {
en: "Personal Information",
fr: "Informations personnelles",
es: "Información Personal"
},
fields: [
{
id: "title",
type: "select",
label: { en: "Title", fr: "Titre", es: "Título" },
options: [
{ value: "mr", label: { en: "Mr", fr: "M.", es: "Sr." } },
{ value: "ms", label: { en: "Ms", fr: "Mme", es: "Sra." } },
{ value: "dr", label: { en: "Dr", fr: "Dr", es: "Dr." } }
]
},
{
id: "firstName",
type: "text",
label: { en: "First Name", fr: "Prénom", es: "Nombre" },
placeholder: {
en: "Enter your first name",
fr: "Entrez votre prénom",
es: "Ingrese su nombre"
},
validations: {
required: {
en: "First name is required",
fr: "Le prénom est obligatoire",
es: "El nombre es obligatorio"
}
}
}
]
}
]
};
Note: The library provides the I18n structure. You'll need to implement locale selection and text resolution in your application layer.
Override any default renderer by supplying your own component:
{
id: "salary",
type: "number",
label: "Salary",
renderer: CurrencyInput
}
This makes the library extensible without modifying core logic.
Validation is handled through the useFormBuilder hook and the imperative form handle.
const {
handleChange, // Function to update a field value: (id, value) => void
validateAll, // Function to validate all fields: () => Record<string, string[]>
getValues, // Function to get all current form values: () => Record<string, any>
getErrors, // Function to get all current form errors: () => Record<string, string | null>
validateField, // Function to validate a specific field: (id) => string[]
} = useFormBuilder(myFormDTO);
validations: {
required: "This field is required",
validate: (value) => value.length < 2 ? "Minimum 2 characters" : null
}
Recommendation: Standardize validation return values to
string[]for predictable handling in large applications.
Backend → returns FormDTO
Frontend → renders form dynamically
Backend updates → UI changes without redeploy
This enables:
registerFieldType)Contributions are welcome and encouraged.
git checkout -b feature/my-featuregit commit -m "Add my feature"git push origin feature/my-featurePlease keep changes focused and well-documented.
MIT
React Form DTO — Schema-first forms for Material UI
FAQs
A React library for building forms using DTOs with MUI and TypeScript.
The npm package react-form-dto receives a total of 1 weekly downloads. As such, react-form-dto popularity was classified as not popular.
We found that react-form-dto demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.

Security News
OpenSSF has issued a high-severity advisory warning open source developers of an active Slack-based campaign using impersonation to deliver malware.

Research
/Security News
Malicious packages published to npm, PyPI, Go Modules, crates.io, and Packagist impersonate developer tooling to fetch staged malware, steal credentials and wallets, and enable remote access.