Field Types
Built-in types, validation constraints, showWhen operators, and $Infer mappings.
Seven built-in types in @dimah-form/core. Each one has a definition schema, a server validator, and an $Infer mapping.
Built-In Field Types Catalog
| Type | Answer Type | Schema Constraints | Description |
|---|---|---|---|
text | string | minLength?: numbermaxLength?: numberpattern?: string (regex) | Single-line or multi-line text input. Validates length and regex pattern. |
number | number | min?: numbermax?: numberinteger?: boolean | Numeric input. Validates minimum, maximum, and integer constraints. |
boolean | boolean | — | Checkbox or toggle switch. Values are strictly true or false. |
select | string | options: SelectOption[] | Single-choice dropdown or radio group. Answer must match an option value. |
multiSelect | string[] | options: SelectOption[] | Multi-choice checkbox group or tag selector. Answer must be an array of valid option values without duplicates. |
email | string | — | Email address input. Validates standard RFC 5322 email formatting. |
date | string | min?: string (YYYY-MM-DD)max?: string (YYYY-MM-DD) | Calendar date in YYYY-MM-DD ISO format. Validates date boundaries. |
Select Option Structure
Options passed to select or multiSelect fields follow this schema:
type SelectOption = {
/** The persisted answer value (e.g., "us", "uk", "de") */
value: string;
/** Display label for the UI (defaults to value if omitted) */
label?: string;
/** Custom metadata for icons, descriptions, or badge tags */
meta?: Record<string, unknown>;
};Conditional Visibility (showWhen)
The showWhen property dynamically hides or shows fields based on the respondent's answers.
Automatic Cleanup: When a field is conditionally hidden, its answer is automatically stripped from the submitted response so outdated answers never persist to your database.
1. Leaf Conditions
equals
Matches when the target field's answer strictly equals the specified value:
{
id: "githubHandle",
type: "text",
label: "GitHub Handle",
showWhen: { field: "role", equals: "developer" },
}notEquals
Matches when the target field's answer is not equal to the specified value:
{
id: "reason",
type: "text",
label: "Reason for cancellation",
showWhen: { field: "plan", notEquals: "free" },
}includes
Matches when a multiSelect array answer contains the specified value:
{
id: "reactExperience",
type: "number",
label: "Years of React Experience",
showWhen: { field: "skills", includes: "react" },
}2. Compound Conditions (all / any)
all (Logical AND)
Matches only when all nested conditions evaluate to true:
showWhen: {
all: [
{ field: "department", equals: "engineering" },
{ field: "isLead", equals: true },
],
}any (Logical OR)
Matches when at least one nested condition evaluates to true:
showWhen: {
any: [
{ field: "country", equals: "US" },
{ field: "country", equals: "CA" },
],
}Type Inference ($Infer) Mapping
When defining forms with defineForm(), TypeScript derives the answer type automatically:
import { defineForm } from "@dimah-form/server";
export const exampleForm = defineForm({
title: "Example",
slug: "example",
fields: [
{ id: "name", type: "text", required: true },
{ id: "age", type: "number" }, // Optional because required is omitted
{
id: "role",
type: "select",
required: true,
options: [{ value: "admin" }, { value: "user" }],
},
{
id: "tags",
type: "multiSelect",
options: [{ value: "frontend" }, { value: "backend" }],
},
],
});The resulting $Infer answer type is strictly typed:
type ExampleAnswers = Form["$Infer"]["answers"]["example"];
// Resolves to:
// {
// name: string;
// role: "admin" | "user";
// age?: number;
// tags?: ("frontend" | "backend")[];
// }