Why not just use MUI + React Hook Form?
They work great.
Until your forms start getting complex.
The Core Pain
When forms need conditional fields and dynamic behavior, glue code appears
React Hook Form handles registration and validation. MUI provides the components. But as soon as you need conditional fields, cross-field validation, or dynamic error handling, you write Controller wrappers, scatter watch() calls across components, and manually wire error state. The glue code accumulates.
The Same Form, Two Approaches
Support form with conditional field: show details when bug is selected
The same form. Two very different approaches.
MUI + React Hook Form
function SupportForm() {
const { control, watch } = useForm();
const category = watch('category');
return (
<form>
<Controller
name="category"
control={control}
rules={{ required: 'Category is required' }}
render={({ field, fieldState }) => (
<Select
{...field}
error={!!fieldState.error}
>
<MenuItem value="bug">Bug</MenuItem>
<MenuItem value="feature">Feature</MenuItem>
<MenuItem value="billing">Billing</MenuItem>
</Select>
)}
/>
{category === 'bug' && (
<Controller
name="details"
control={control}
rules={{
validate: (value) =>
value?.trim()
? true
: 'Details required for bugs'
}}
render={({ field, fieldState }) => (
<TextField
{...field}
label="Bug Details"
error={!!fieldState.error}
helperText={fieldState.error?.message}
/>
)}
/>
)}
</form>
);
}Controller + watch + conditional JSX + manual error wiring
Dashforge
function SupportForm() {
return (
<DashForm onSubmit={handleSubmit}>
<Select
name="category"
label="Category"
options={[
{ value: 'bug', label: 'Bug' },
{ value: 'feature', label: 'Feature' },
{ value: 'billing', label: 'Billing' },
]}
rules={{ required: 'Category is required' }}
/>
<TextField
name="details"
label="Bug Details"
visibleWhen={(engine) =>
engine.getNode('category')?.value === 'bug'
}
rules={{
validate: (value, form) =>
value?.trim()
? true
: 'Details required for bugs'
}}
/>
</DashForm>
);
}No Controller. No watch. Just declarative fields.
Why This Matters at Scale
The difference compounds as forms grow
A simple login form has minimal glue code.
But forms with 10+ fields, conditional sections, cross-field validation, and dynamic behavior become orchestration nightmares.
Dashforge moves that orchestration into the framework. Validation, visibility, and error handling stay close to the field. As forms scale, your code stays clean.
What Dashforge Changes
Three practical improvements
No Controller. Ever.
Fields register themselves. Errors bind automatically. Touched state handled internally. Write the field once—not wrapped in render props.
Show/hide fields declaratively
Use visibleWhen on the field instead of watch() + conditional JSX in the parent. The dependency lives where it belongs.
Validation stays with the field
Cross-field rules access form state without manual wiring. Validation logic lives next to the field it validates.
Ready to Install?
If this looks like a better way to structure forms, start by installing Dashforge.
Installation →