Why Dashforge | Dashforge-UI
DocsStarter Kits
v0.1.0-alpha

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 →
On This Page