Types Reference

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:

import type { FormConfig, FieldConfig, Step, SubmitActionNode } from '@saastro/forms';

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().

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

FormLayout

Controls the form grid system.

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

Breakpoint

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

SubmitConfirmationConfig

Double-click confirmation for forms with optional fields.

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

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.

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


Fields

FieldConfig

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

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 for hidden and resolvers.

BaseFieldProps

All field types extend this base:

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

Key properties inherited by all fields:

CategoryProperties
Labellabel, hideLabel, label_className, description
Layoutlayout, columns, wrapper_className, className
Inputinput_className, value, defaultValue, placeholder, autocomplete, required, size, icon, iconProps
Validationschema, customValidators, error_className, helperText, helper_className, tooltip
Statehidden, disabled, readOnly
Transformtransform
Computedcomputed{ dependsOn: string[]; compute: (values) => unknown }
AdvancedtrackingKey

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

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:

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

ButtonCardOption

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

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

Buttons

ButtonConfig

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, or globally with setDefaultMessages.

FormButtons

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

See: Buttons


Steps

Step

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

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

See: Multi-Step Forms


Conditional logic

ConditionOperator

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

Condition

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

ConditionGroup

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

See: Conditional Logic, Multi-Step Forms


Submit actions

SubmitActionNode

The complete configuration for a single submit action:

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

SubmitAction (union)

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

SubmitActionCondition

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

SubmitTrigger

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

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

FieldMapping

// 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:

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

SubmitExecutionConfig

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

SubmitActionsResult

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


Plugins

FormPlugin

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

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

definePlugin()

function definePlugin(plugin: FormPlugin): FormPlugin;

See: Plugins


Validation

ValidationRules

Serializable validation rules compiled to Zod at runtime:

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

type SchemaType = z.ZodType | ValidationRules;

See: Validation


Components

ComponentRegistry

The full 49-slot interface for UI component injection. See Component System for the complete table.

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

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