Browse docs
Browse docs
Complete reference for Form System APIs.
The DashForm component wraps your form and provides the reactive engine, reactions, and runtime store.
import { DashForm } from '@dashforge/forms';
<DashForm
reactions={reactions}
defaultValues={{ country: 'United States' }}
onSubmit={(data) => console.log(data)}
>
{/* form fields */}
</DashForm>| Prop | Type | Default | Description |
|---|---|---|---|
reactions | ReactionDefinition[] | [] | Array of reaction definitions for dynamic behavior |
resolver | Resolver<FormValues> | — | Optional schema-based validation resolver (Zod, Yup, Joi, etc.) |
onSubmit | (data: FormValues) => void | Promise<void> | required | Form submission handler |
defaultValues | Partial<FormValues> | — | Initial form values |
children | React.ReactNode | required | Form fields and content |
The resolver prop accepts a React Hook Form resolver function. Resolvers translate validation schemas into error objects. This enables schema-based validation instead of field-level rules.
| Approach | Prop | Description |
|---|---|---|
| Field Rules | rules prop | Simple field-level validation (required, min, max, pattern) |
| Resolver | resolver prop | Schema-based validation with type safety and reusable schemas |
| Reactions | reactions array | Dynamic cross-field validation triggered by value changes |
npm install zod @hookform/resolversimport { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { DashForm } from '@dashforge/forms';
import { TextField, NumberField } from '@dashforge/tw';
const schema = z.object({
email: z.string().email('Invalid email address'),
age: z.number().min(18, 'Must be at least 18'),
password: z.string().min(8, 'Password must be at least 8 characters'),
});
type FormValues = z.infer<typeof schema>;
function RegistrationForm() {
const handleSubmit = (data: FormValues) => {
console.log('Valid data:', data);
};
return (
<DashForm
resolver={zodResolver(schema)}
defaultValues={{ email: '', age: 0, password: '' }}
onSubmit={handleSubmit}
>
<TextField name="email" label="Email" />
<NumberField name="age" label="Age" />
<TextField name="password" label="Password" type="password" />
</DashForm>
);
}Here is a live, runnable version — blur a field empty or invalid to see the Zod messages surface through the normal Dashforge error path. Use the StackBlitz button on the preview toolbar to open it as a full project:
import { DashForm } from '@dashforge/forms';
import { TextField, Box, Stack, Button } from '@dashforge/tw';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
// Dashforge does NOT bundle a validation library.
// Install your own: npm install zod @hookform/resolvers
const schema = z.object({
email: z
.string()
.min(1, 'Email is required')
.email('Enter a valid email address'),
age: z
.string()
.min(1, 'Age is required')
.refine((v) => Number(v) >= 18, 'You must be 18 or older'),
});
type SignupForm = z.infer<typeof schema>;
export function SignupForm() {
return (
<Box sx="w-full max-w-md">
<DashForm<SignupForm>
resolver={zodResolver(schema)}
defaultValues={{ email: '', age: '' }}
mode="onBlur"
onSubmit={(data) => console.log('Valid!', data)}
>
<Stack gap={2}>
<TextField name="email" label="Email" fullWidth />
<TextField name="age" label="Age" fullWidth />
<Button type="submit" variant="solid" sx="self-start">
Submit
</Button>
</Stack>
</DashForm>
</Box>
);
}npm install yup @hookform/resolversimport { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';
import { DashForm } from '@dashforge/forms';
import { TextField } from '@dashforge/tw';
const schema = yup.object({
email: yup.string().email('Invalid email').required('Email is required'),
password: yup.string().min(8, 'Password too short').required('Required'),
});
function LoginForm() {
return (
<DashForm
resolver={yupResolver(schema)}
defaultValues={{ email: '', password: '' }}
onSubmit={(data) => console.log(data)}
>
<TextField name="email" label="Email" />
<TextField name="password" label="Password" type="password" />
</DashForm>
);
}Reactions are defined as objects with these properties:
| Prop | Type | Default | Description |
|---|---|---|---|
id | string | required | Unique identifier |
watch | string[] | required | Field names to watch |
when | (ctx: WhenContext) => boolean | — | Optional condition |
run | (ctx: RunContext) => void | Promise<void> | required | Effect to execute |
The run context provides methods for reading/writing form state:
| Method | Type | Description |
|---|---|---|
getValue<T>(name) | (name: string) => T | Read field value |
setValue(name, value) | (name: string, value: unknown) => void | Update field value |
getRuntime<TData>(name) | (name: string) => FieldRuntimeState<TData> | Read runtime state |
setRuntime<TData>(name, patch) | (name: string, patch: Partial<FieldRuntimeState<TData>>) => void | Update runtime state |
beginAsync(key) | (key: string) => number | Start async operation |
isLatest(key, id) | (key: string, requestId: number) => boolean | Check if response is valid |
Runtime state stores async metadata separate from form values:
interface FieldRuntimeState<TData = unknown> {
status: 'idle' | 'loading' | 'ready' | 'error';
data: TData | null;
error: string | null;
}| Prop | Type | Default | Description |
|---|---|---|---|
status | 'idle' | 'loading' | 'ready' | 'error' | 'idle' | Current operation status |
data | TData | null | null | Runtime data (e.g., options) |
error | string | null | null | Error message if status is 'error' |
All form components accept a visibleWhen prop for conditional rendering:
visibleWhen?: (engine: Engine) => boolean
// Example
<TextField
name="companyName"
label="Company Name"
visibleWhen={(engine) =>
engine.getNode('accountType')?.value === 'business'
}
/>Select and Autocomplete components can load options from runtime state:
<Select
name="state"
label="State"
optionsFromFieldData
/>
// Runtime state structure expected:
{
status: 'ready',
data: {
options: ['California', 'Texas', 'New York']
}
}