# Types Reference

URL: https://docs.forms.saastro.io/docs/types
> Comprehensive reference for the TypeScript types exported by @saastro/forms.

# Types Reference

Every type on this page is importable from the single `@saastro/forms` entry point — the package has no subpath exports:

```tsx
```

The reference is organized by concept. Types that have dedicated guide pages link to them for detailed usage.

---

## Form configuration

### FormConfig

The top-level configuration object consumed by `<Form config={...} />` and returned by `FormBuilder.build()`.

```tsx
interface FormConfig {
  formId: string;
  fields: Record<string, FieldConfig>;
  steps: Record<string, Step>; // keyed by step id — a Record, not an array
  initialStep?: string;
  layout?: FormLayout; // grid settings — columns lives here, not top-level
  buttons?: FormButtons;
  submitConfirmation?: SubmitConfirmationConfig;
  pluginManager?: PluginManager;
  successMessage?: string | ((values: Record<string, unknown>) => string);
  errorMessage?: string | ((error: Error, values: Record<string, unknown>) => string);
  redirect?: string | ((values: Record<string, unknown>) => string);
  submitActions?: Record<string, SubmitActionNode>;
  submitExecution?: SubmitExecutionConfig;
  locale?: string; // active locale to render (applies the matching i18n overlay)
  i18n?: FormI18n; // per-locale translation overlays
  onStepChange?: (step: string) => void;
  onSuccess?: (values: Record<string, unknown>) => void;
  onError?: (error: Error, values: Record<string, unknown>) => void;
  /** @deprecated Use submitActions instead */
  submit?: SubmitConfig;
}
```

Notes:

- The identifier property is `formId` (not `id`).
- `steps` is a `Record<string, Step>` — the record key is the step id.
- Column count is configured under `layout` (`FormLayout`), never at the top level.

See: [FormBuilder API](/docs/formbuilder)

### FormLayout

Controls the form grid system.

```tsx
interface FormLayout {
  mode: 'auto' | 'manual';
  gap?: GapValue; // 0-12 (default: 4)
  minFieldWidth?: number; // Pixels, auto mode only (default: 240)
  columns?: ColumnsValue; // 1-12
  className?: string; // Extra CSS classes for grid container
}
```

See: [Layout System](/docs/layout)

### Breakpoint

```tsx
type Breakpoint = 'default' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
```

### SubmitConfirmationConfig

Double-click confirmation for forms with optional fields.

```tsx
interface SubmitConfirmationConfig {
  optionalFields: Array<{ name: string; message: string }>;
  buttonBehavior: {
    defaultText?: string;
    warningText: string;
    warningVariant?: ButtonConfig['variant'];
    warningEffect?: ButtonConfig['effect'];
    resetDelay?: number; // ms, default: 3000
  };
  applyOn: 'submit' | 'steps' | 'both';
  showOnce?: boolean; // default: true
}
```

See: [FormBuilder API](/docs/formbuilder#submit-confirmation)

### LocaleOverlay & FormI18n

Translation overlays merged over the base config when `FormConfig.locale` matches a key in `i18n.translations`. Only the strings you specify are overridden; everything else falls back to the base config.

```tsx
interface LocaleOverlay {
  fields?: Record<
    string,
    {
      label?: string;
      placeholder?: string;
      helperText?: string;
      description?: string;
      options?: Array<{ value: string | number; label: string }>; // matched to base options by value
    }
  >;
  buttons?: {
    submit?: { label?: string };
    next?: { label?: string };
    back?: { label?: string };
  };
  steps?: Record<string, { title?: string; description?: string }>;
  messages?: { success?: string; error?: string };
}

interface FormI18n {
  defaultLocale?: string; // language of the base config strings
  locales?: string[]; // available locales
  translations?: Record<string, LocaleOverlay>;
}
```

The library ships built-in English defaults (button labels `Submit`/`Next →`/`← Back`, the `✅ Thank you!` success message, placeholders like `Select an option...`); use these overlays to translate your forms, or flip the package defaults globally with `setDefaultMessages`. See: [Internationalization](/docs/i18n)

---

## Fields

### FieldConfig

The discriminated union of every field configuration: **29 union members** covering the **37 field `type` strings** (some interfaces serve several `type` values).

```tsx
type FieldConfig =
  | TextFieldProps // type: 'text' | 'email' | 'tel' | 'url' | 'password' | 'number'
  | TextareaFieldProps // type: 'textarea'
  | SliderFieldProps // type: 'slider'
  | RangeFieldProps // type: 'range' — renders identically to slider
  | RatingFieldProps // type: 'rating' — star rating, integer 1..max
  | MatrixFieldProps // type: 'matrix' — Likert grid, value keyed by row
  | PhoneFieldProps // type: 'phone' — international phone, value is E.164
  | SelectFieldProps // type: 'select'
  | ComboboxFieldProps // type: 'combobox'
  | NativeSelectFieldProps // type: 'native-select'
  | RadioFieldProps // type: 'radio'
  | ButtonRadioFieldProps // type: 'button-radio'
  | CheckboxFieldProps // type: 'checkbox'
  | SwitchFieldProps // type: 'switch'
  | CheckboxGroupFieldProps // type: 'checkbox-group'
  | SwitchGroupFieldProps // type: 'switch-group'
  | ButtonCheckboxFieldProps // type: 'button-checkbox'
  | DateFieldProps // type: 'date'
  | DateRangeFieldProps // type: 'daterange'
  | HtmlFieldProps // type: 'html'
  | ButtonCardFieldProps // type: 'button-card'
  | OtpFieldProps // type: 'otp'
  | CommandFieldProps // type: 'command' — same popover UI as combobox
  | InputGroupFieldProps // type: 'input-group'
  | CurrencyFieldProps // type: 'currency' — renders identically to input-group
  | FileFieldProps // type: 'file'
  | RepeaterFieldProps // type: 'repeater' — nested sub-fields
  | HiddenFieldProps // type: 'hidden' — renders null; requires a SerializableFieldResolver
  | ButtonConfig; // type: 'button' | 'submit' | 'next' | 'back'
```

See: [Hidden Fields](/docs/hidden-fields) for `hidden` and resolvers.

### BaseFieldProps

All field types extend this base:

```tsx
interface BaseFieldProps
  extends
    LabelProps,
    LayoutProps,
    InputProps,
    ValidationProps,
    StateProps,
    TransformProps,
    ComputedProps,
    AdvancedProps {}
```

Key properties inherited by all fields:

| Category       | Properties                                                                                                         |
| -------------- | ------------------------------------------------------------------------------------------------------------------ |
| **Label**      | `label`, `hideLabel`, `label_className`, `description`                                                             |
| **Layout**     | `layout`, `columns`, `wrapper_className`, `className`                                                              |
| **Input**      | `input_className`, `value`, `defaultValue`, `placeholder`, `autocomplete`, `required`, `size`, `icon`, `iconProps` |
| **Validation** | `schema`, `customValidators`, `error_className`, `helperText`, `helper_className`, `tooltip`                       |
| **State**      | `hidden`, `disabled`, `readOnly`                                                                                   |
| **Transform**  | `transform`                                                                                                        |
| **Computed**   | `computed` — `{ dependsOn: string[]; compute: (values) => unknown }`                                               |
| **Advanced**   | `trackingKey`                                                                                                      |

Two layout props exist in the type but are **declared and not yet implemented** by the runtime: field-level `gridSpan` and field-level `order`. Use `layout.columns` and `layout.order` (via `FieldLayoutConfig`) instead.

### FieldLayoutConfig

```tsx
interface FieldLayoutConfig {
  columns?: Partial<Record<Breakpoint, number>>; // 1-12 per breakpoint
  order?: number | Partial<Record<Breakpoint, number>>;
}
```

### Option

Used by `select`, `native-select`, `combobox`, `command`, `radio`, `button-radio`, `checkbox-group`, `switch-group`, and `button-checkbox`:

```tsx
type Option = {
  label: string;
  value: string;
  icon?: ReactNode;
  iconProps?: Record<string, unknown>;
};
```

### ButtonCardOption

Used by `button-card` (note `text` instead of `label`):

```tsx
type ButtonCardOption = {
  value: string;
  text: string;
  description?: string;
  icon?: ReactNode;
  iconProps?: Record<string, unknown>;
};
```

---

## Buttons

### ButtonConfig

```tsx
interface ButtonConfig extends BaseFieldProps {
  type: 'button' | 'submit' | 'next' | 'back';
  action?: () => void;
  loading?: boolean;
  icon?: ReactNode;
  iconPosition?: 'left' | 'right';
  variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
  effect?: string; // declared but not applied by the built-in button renderer
  rounded?: string; // declared but not applied by the built-in button renderer
  label?: string;
}
```

`effect` and `rounded` are typed as plain strings; the built-in renderer currently ignores them, so any visual effect depends on your own styling (e.g. via `className`). Default button labels are English (`Submit`, `Next →`, `← Back`) — override per button via `label`, per locale via [i18n](/docs/i18n), or globally with `setDefaultMessages`.

### FormButtons

```tsx
interface FormButtons {
  submit?: ButtonConfig;
  next?: ButtonConfig;
  back?: ButtonConfig;
  align?: 'start' | 'center' | 'end' | 'between' | 'responsive';
  inline?: boolean;
}
```

See: [Buttons](/docs/buttons)

---

## Steps

### Step

```tsx
type Step = {
  id?: string; // Optional — the Record key serves as id
  title?: string; // Shown by multi-step UIs; i18n-translatable
  description?: string; // i18n-translatable
  fields: string[]; // Field names in this step
  next?: StepCondition[];
  defaultNext?: string; // Fallback if no condition matches
};
```

### StepCondition

```tsx
type StepCondition = ConditionGroup & {
  target: string; // Step id to navigate to
};
```

See: [Multi-Step Forms](/docs/multi-step)

---

## Conditional logic

### ConditionOperator

```tsx
type ConditionOperator =
  | 'equals'
  | 'notEquals'
  | 'contains'
  | 'notContains'
  | 'greaterThan'
  | 'lessThan'
  | 'greaterThanOrEqual'
  | 'lessThanOrEqual'
  | 'isTrue'
  | 'isFalse'
  | 'isEmpty'
  | 'isNotEmpty';
```

### Condition

```tsx
type Condition = {
  field: string;
  operator: ConditionOperator;
  value?: string | number | boolean | null;
};
```

### ConditionGroup

```tsx
type ConditionGroup = {
  conditions: Condition[];
  operator: 'AND' | 'OR';
};
```

See: [Conditional Logic](/docs/conditional-logic), [Multi-Step Forms](/docs/multi-step)

---

## Submit actions

### SubmitActionNode

The complete configuration for a single submit action:

```tsx
interface SubmitActionNode {
  id: string;
  action: SubmitAction;
  trigger: SubmitTrigger;
  condition?: SubmitActionCondition;
  order?: number;
  continueOnError?: boolean;
  disabled?: boolean;
  fieldMapping?: FieldMappingConfig;
}
```

### SubmitAction (union)

```tsx
type SubmitAction =
  | HttpSubmitAction // type: 'http'
  | WebhookSubmitAction // type: 'webhook'
  | EmailSubmitAction // type: 'email'
  | CustomSubmitAction // type: 'custom'
  | IntegrationSubmitAction; // type: 'integration' — server-side only
```

`IntegrationSubmitAction` (`{ type: 'integration'; name: string; integrationRef: string; fieldMapping?: Record<string, string> }`) is **not executed by the in-browser runtime** — it declares an integration that the hosted submit backend dispatches server-side after receiving the submission. See: [HubForm & hosted submit](/docs/hub-form)

### SubmitActionCondition

```tsx
interface SubmitActionCondition {
  field: string;
  operator: ConditionOperator;
  value: unknown;
}
```

### SubmitTrigger

```tsx
type SubmitTriggerType =
  | 'onSubmit'
  | 'onStepEnter'
  | 'onStepExit'
  | 'onFieldChange'
  | 'onFieldBlur'
  | 'onDelay'
  | 'manual';

interface SubmitTrigger {
  type: SubmitTriggerType;
  stepId?: string;
  fieldName?: string;
  delayMs?: number;
  debounceMs?: number;
}
```

### FieldMapping

```tsx
// Two formats: simple rename map, or the advanced object
type FieldMappingConfig = Record<string, string> | FieldMapping;

interface FieldMapping {
  fields?: Record<string, string | FieldMapEntry>;
  inject?: Record<string, unknown>; // static values or { $resolver: ... }
  exclude?: string[];
  passthrough?: boolean; // default: true
}

interface FieldMapEntry {
  to: string;
  transform?: BuiltinTransform | FieldTransformFn;
}

type BuiltinTransform =
  | 'toString'
  | 'toNumber'
  | 'toBoolean'
  | 'booleanString'
  | 'dateISO'
  | 'dateYMD'
  | 'dateDMY'
  | 'dateTimestamp'
  | 'trim'
  | 'lowercase'
  | 'uppercase'
  | 'emptyToNull';
```

### FieldResolver

Injects computed/dynamic values into a payload (in `FieldMapping.inject`) or into `hidden` fields. Eight variants:

```tsx
type FieldResolver =
  | { $resolver: 'timestamp' }
  | { $resolver: 'hostname' }
  | { $resolver: 'urlParam'; param: string; fallback?: string }
  | { $resolver: 'ip'; endpoint?: string; fallback?: string }
  | { $resolver: 'pageUrl' }
  | { $resolver: 'referrer' }
  | { $resolver: 'userAgent' }
  | { $resolver: 'custom'; fn: () => unknown };

// JSON-serializable subset (excludes 'custom') — required by hidden fields
type SerializableFieldResolver = Exclude<FieldResolver, { $resolver: 'custom' }>;
```

The package also exports `BUILTIN_RESOLVERS`, a constant listing the 7 serializable resolvers with `{ id, label, description }` metadata. See: [Hidden Fields](/docs/hidden-fields)

### SubmitExecutionConfig

```tsx
interface SubmitExecutionConfig {
  mode: 'sequential' | 'parallel';
  stopOnFirstError?: boolean;
  globalTimeout?: number;
}
```

### SubmitActionsResult

```tsx
interface SubmitActionsResult {
  results: SubmitActionResult[];
  allSuccessful: boolean;
  successCount: number;
  failureCount: number;
  totalDurationMs: number;
}

interface SubmitActionResult {
  actionId: string;
  actionName: string;
  success: boolean;
  data?: unknown;
  error?: Error;
  durationMs: number;
  startedAt: Date;
  completedAt: Date;
}
```

### Legacy submit (deprecated)

`SubmitConfig` (`DefaultSubmitConfig | CustomSubmitConfig`) backs the deprecated `FormConfig.submit` property — prefer `submitActions`. `CustomSubmitConfig` (`{ type: 'custom'; onSubmit: (values) => Promise<void> }`) is still relevant: it is the return type of `createHubFormSubmit` from the hosted submit integration.

See: [Submit & Actions](/docs/submit)

---

## Plugins

### FormPlugin

```tsx
interface FormPlugin {
  name: string;
  version: string;
  description?: string;
  options?: Record<string, unknown>;
  init?: (options?: Record<string, unknown>) => void;
  cleanup?: () => void;
  onFormInit?: (config: FormConfig) => void;
  onFieldChange?: (fieldName: string, value: unknown, allValues: Record<string, unknown>) => void;
  onStepChange?: (stepId: string, values: Record<string, unknown>) => void;
  onBeforeSubmit?: (values: Record<string, unknown>) => void | Promise<void>;
  onAfterSubmit?: (values: Record<string, unknown>, response: unknown) => void;
  onError?: (error: Error, values?: Record<string, unknown>) => void;
  transformConfig?: (config: FormConfig) => FormConfig;
  transformValues?: (
    values: Record<string, unknown>,
  ) => Record<string, unknown> | Promise<Record<string, unknown>>;
  registerFields?: () => Record<
    string,
    React.ComponentType<{ name: string; fieldConfig: any; control: any; colSpanItem: string }>
  >;
  validators?: Record<
    string,
    (value: unknown, context: ValidationContext) => boolean | string | Promise<boolean | string>
  >;
}
```

Note: the renderer type used by `registerFields` is shown inline because it is not exported as a named type. The name `FieldRenderer` that **is** exported from `@saastro/forms` is the React component that renders a single field — not a type.

### ValidationContext

```tsx
interface ValidationContext {
  fieldName: string;
  allValues: Record<string, unknown>;
  abortSignal?: AbortSignal;
}
```

### definePlugin()

```tsx
function definePlugin(plugin: FormPlugin): FormPlugin;
```

See: [Plugins](/docs/plugins)

---

## Validation

### ValidationRules

Serializable validation rules compiled to Zod at runtime:

```tsx
type ValidationRules = {
  required?: boolean;
  requiredMessage?: string;
  minLength?: number;
  minLengthMessage?: string;
  maxLength?: number;
  maxLengthMessage?: string;
  pattern?: string;
  patternMessage?: string;
  format?: 'email' | 'url' | 'uuid' | 'cuid' | 'emoji';
  formatMessage?: string;
  min?: number;
  minMessage?: string;
  max?: number;
  maxMessage?: string;
  integer?: boolean;
  integerMessage?: string;
  positive?: boolean;
  positiveMessage?: string;
  minItems?: number;
  minItemsMessage?: string;
  maxItems?: number;
  maxItemsMessage?: string;
  mustBeTrue?: boolean;
  mustBeTrueMessage?: string;
  minDate?: string;
  minDateMessage?: string;
  maxDate?: string;
  maxDateMessage?: string;
  mustBeFuture?: boolean;
  mustBeFutureMessage?: string;
  mustBePast?: boolean;
  mustBePastMessage?: string;
  preset?: string;
};
```

### SchemaType

```tsx
type SchemaType = z.ZodType | ValidationRules;
```

See: [Validation](/docs/validation)

---

## Components

### ComponentRegistry

The full 49-slot interface for UI component injection. See [Component System](/docs/components) for the complete table.

```tsx
interface ComponentRegistry {
  // Inputs (4): Input, Textarea, Button, Label
  // Checkbox & Switch (2): Checkbox, Switch
  // Radio (2): RadioGroup, RadioGroupItem
  // Select (5): Select, SelectTrigger, SelectContent, SelectItem, SelectValue
  // Native Select (1): NativeSelect
  // Slider (1): Slider
  // Popover (3): Popover, PopoverTrigger, PopoverContent
  // Tooltip (4): Tooltip, TooltipTrigger, TooltipContent, TooltipProvider
  // Separator (1): Separator
  // Dialog (6): Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription
  // Command (6): Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem
  // Input OTP (3): InputOTP, InputOTPGroup, InputOTPSlot
  // Accordion (4): Accordion, AccordionItem, AccordionTrigger, AccordionContent
  // Calendar (1): Calendar
  // Form (2): FormField, FormControl
  // Field (4): Field, FieldLabel, FieldDescription, FieldError
}
```

### Helper Types

```tsx
type PartialComponentRegistry = Partial<ComponentRegistry>;
type ComponentRegistryInput = { [K in keyof ComponentRegistry]: React.ComponentType<any> };
type ComponentOverrides = Partial<{ [K in keyof ComponentRegistry]: React.ComponentType<any> }>;
```

See: [Component System](/docs/components)

---

## Inferring value types

`InferFormValues<T>` derives the TypeScript type of a form's submitted values from a **literal** config (`satisfies` pattern):

```tsx

const config = {
  formId: 'contact',
  fields: {
    email: { type: 'email', label: 'Email', schema: z.string().email() },
    newsletter: { type: 'checkbox', label: 'Subscribe' },
    topics: { type: 'checkbox-group', label: 'Topics', options: [] },
  },
  steps: { main: { fields: ['email', 'newsletter', 'topics'] } },
} as const satisfies FormConfig;

type Values = InferFormValues<typeof config>;
// { email: string; newsletter: boolean; topics: string[] }
```

Rules:

- An explicit **Zod schema wins** (`z.infer`); otherwise the type is derived from the field `type` (checkbox/switch → `boolean`, `*-group` → `string[]`, number/slider → `number`, daterange → `{ from; to }`, repeater → `Record<string, unknown>[]`, …).
- Action/content fields (`button`, `submit`, `next`, `back`, `html`) are omitted from the map.
- `InferFieldValue<F>` is also exported for single fields.

Caveats (this is a type-only helper — zero runtime):

- It only works with **literal** configs. FormBuilder chains collapse to `FormConfig` and erase the literals — use the raw-config `satisfies` pattern when you want typed values.
- Package callbacks still receive `Record<string, unknown>`; cast at the boundary: `onSuccess={(values) => { const v = values as InferFormValues<typeof config>; }}`.
- It reflects the **validated/submitted** value; pre-validation, number inputs may hold strings (DOM behavior).
- Custom plugin field types (open strings) infer to `string`.
