Form Fields Guide

Learn how to work with different field types including text inputs, checkboxes, dates, and initials.

Field Types Overview

The MIMS SDK supports five field types:

  • text - Single or multi-line text input
  • signature - Digital signature capture
  • checkbox - Boolean check/uncheck field
  • date - Date picker field
  • initials - Small signature for initials

Text Fields

Text fields are the most common field type for capturing user input:

Text Field Definitiontsx
const textField: TextFieldDefinition = {
  id: 'field_name',
  type: 'text',
  page: 1,
  x: 0.15,
  y: 0.2,
  width: 0.4,
  height: 0.04,
  required: true,
  editable: true,
  label: 'Full Name',
  placeholder: 'Enter your full legal name',
  maxLength: 100,
  fontSize: 14,
  fontFamily: 'Helvetica',
  textAlign: 'left',
  multiline: false,
};

Text Field Properties

PropTypeDefaultDescription
maxLengthnumberMaximum character length.
fontSizenumber12Font size in points.
fontFamilystring"Helvetica"Font family name.
textAlign"left" | "center" | "right""left"Text alignment.
multilinebooleanfalseAllow multiple lines.

Text Field Component

TextFieldInput.tsxtsx
import { useDocumentStore, type FieldState, type TextFieldValue } from '@mims/sdk-react';

interface TextFieldInputProps {
  field: FieldState;
}

export function TextFieldInput({ field }: TextFieldInputProps) {
  const setFieldValue = useDocumentStore((s) => s.setFieldValue);
  const selectField = useDocumentStore((s) => s.selectField);
  
  const definition = field.definition as TextFieldDefinition;
  const value = (field.value as TextFieldValue)?.content || '';

  const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
    let content = e.target.value;
    
    // Enforce max length
    if (definition.maxLength && content.length > definition.maxLength) {
      content = content.slice(0, definition.maxLength);
    }
    
    setFieldValue(field.definition.id, { type: 'text', content });
  };

  const inputProps = {
    value,
    onChange: handleChange,
    onFocus: () => selectField(field.definition.id),
    placeholder: definition.placeholder,
    style: {
      fontSize: definition.fontSize,
      fontFamily: definition.fontFamily,
      textAlign: definition.textAlign,
    },
    className: 'w-full h-full bg-transparent outline-none',
  };

  return definition.multiline ? (
    <textarea {...inputProps} />
  ) : (
    <input type="text" {...inputProps} />
  );
}

Checkbox Fields

Checkbox fields allow users to agree to terms, select options, or make yes/no choices:

Checkbox Fieldtsx
const checkboxField: CheckboxFieldDefinition = {
  id: 'field_agree',
  type: 'checkbox',
  page: 1,
  x: 0.1,
  y: 0.85,
  width: 0.03,
  height: 0.03,
  required: true,
  editable: true,
  label: 'I agree to the terms and conditions',
  checkStyle: 'check', // 'check' | 'cross' | 'circle'
};

// Checkbox value
const checkboxValue: CheckboxFieldValue = {
  type: 'checkbox',
  checked: true,
};

Checkbox Properties

PropTypeDefaultDescription
checkStyle"check" | "cross" | "circle""check"Style of the check mark.

Checkbox Component

CheckboxFieldInput.tsxtsx
import { useDocumentStore, type FieldState, type CheckboxFieldValue } from '@mims/sdk-react';

interface CheckboxFieldInputProps {
  field: FieldState;
}

export function CheckboxFieldInput({ field }: CheckboxFieldInputProps) {
  const setFieldValue = useDocumentStore((s) => s.setFieldValue);
  
  const definition = field.definition as CheckboxFieldDefinition;
  const checked = (field.value as CheckboxFieldValue)?.checked || false;

  const handleToggle = () => {
    setFieldValue(field.definition.id, { type: 'checkbox', checked: !checked });
  };

  const renderCheckMark = () => {
    if (!checked) return null;
    
    switch (definition.checkStyle) {
      case 'cross':
        return <span className="text-lg">×</span>;
      case 'circle':
        return <span className="w-2 h-2 bg-black rounded-full" />;
      default:
        return <span className="text-lg">✓</span>;
    }
  };

  return (
    <button
      onClick={handleToggle}
      className={`
        w-full h-full border-2 rounded flex items-center justify-center
        ${checked ? 'border-emerald-500 bg-emerald-50' : 'border-neutral-300'}
      `}
    >
      {renderCheckMark()}
    </button>
  );
}

Date Fields

Date fields provide a date picker for selecting dates:

Date Fieldtsx
const dateField: DateFieldDefinition = {
  id: 'field_date',
  type: 'date',
  page: 1,
  x: 0.6,
  y: 0.2,
  width: 0.25,
  height: 0.04,
  required: true,
  editable: true,
  label: 'Date of Birth',
  dateFormat: 'MM/DD/YYYY',
  minDate: '1900-01-01',
  maxDate: new Date().toISOString().split('T')[0], // Today
};

// Date value (always ISO format internally)
const dateValue: DateFieldValue = {
  type: 'date',
  value: '1990-05-15', // ISO 8601 format
};

Date Field Properties

PropTypeDefaultDescription
dateFormatstring"MM/DD/YYYY"Display format for the date.
minDatestringMinimum allowed date (ISO format).
maxDatestringMaximum allowed date (ISO format).

Date Field Component

DateFieldInput.tsxtsx
import { useDocumentStore, type FieldState, type DateFieldValue } from '@mims/sdk-react';

interface DateFieldInputProps {
  field: FieldState;
}

export function DateFieldInput({ field }: DateFieldInputProps) {
  const setFieldValue = useDocumentStore((s) => s.setFieldValue);
  const selectField = useDocumentStore((s) => s.selectField);
  
  const definition = field.definition as DateFieldDefinition;
  const value = (field.value as DateFieldValue)?.value || '';

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const dateValue = e.target.value;
    
    // Validate against min/max
    if (definition.minDate && dateValue < definition.minDate) return;
    if (definition.maxDate && dateValue > definition.maxDate) return;
    
    setFieldValue(field.definition.id, { type: 'date', value: dateValue });
  };

  // Format display value
  const formatDate = (isoDate: string) => {
    if (!isoDate) return '';
    const date = new Date(isoDate);
    const format = definition.dateFormat || 'MM/DD/YYYY';
    
    const parts = {
      MM: String(date.getMonth() + 1).padStart(2, '0'),
      DD: String(date.getDate()).padStart(2, '0'),
      YYYY: String(date.getFullYear()),
      YY: String(date.getFullYear()).slice(-2),
    };
    
    return format.replace(/MM|DD|YYYY|YY/g, (match) => parts[match as keyof typeof parts]);
  };

  return (
    <input
      type="date"
      value={value}
      onChange={handleChange}
      onFocus={() => selectField(field.definition.id)}
      min={definition.minDate}
      max={definition.maxDate}
      className="w-full h-full bg-transparent outline-none"
    />
  );
}

Initials Fields

Initials fields are smaller signature fields for initialing pages or sections:

Initials Fieldtsx
const initialsField: InitialsFieldDefinition = {
  id: 'field_initials_1',
  type: 'initials',
  page: 1,
  x: 0.85,
  y: 0.95,
  width: 0.1,
  height: 0.04,
  required: true,
  editable: true,
  label: 'Initials',
};

// Initials value
const initialsValue: InitialsFieldValue = {
  type: 'initials',
  imageData: 'data:image/png;base64,...', // Or:
  typedText: 'JD',
};

Dynamic Field Rendering

Create a component that renders the appropriate input based on field type:

FieldRenderer.tsxtsx
import { type FieldState } from '@mims/sdk-react';

interface FieldRendererProps {
  field: FieldState;
  onOpenSignatureModal: (fieldId: string) => void;
}

export function FieldRenderer({ field, onOpenSignatureModal }: FieldRendererProps) {
  switch (field.definition.type) {
    case 'text':
      return <TextFieldInput field={field} />;
      
    case 'checkbox':
      return <CheckboxFieldInput field={field} />;
      
    case 'date':
      return <DateFieldInput field={field} />;
      
    case 'signature':
      return (
        <SignatureFieldOverlay 
          field={field} 
          onClick={() => onOpenSignatureModal(field.definition.id)}
        />
      );
      
    case 'initials':
      return (
        <InitialsFieldOverlay
          field={field}
          onClick={() => onOpenSignatureModal(field.definition.id)}
        />
      );
      
    default:
      return null;
  }
}

Field Validation

Implement comprehensive validation for all field types:

Field Validationtsx
import type { FieldState, AnyFieldValue } from '@mims/sdk-react';

export function validateField(field: FieldState): string[] {
  const errors: string[] = [];
  const { definition, value } = field;

  // Required check
  if (definition.required && !value) {
    errors.push('This field is required');
    return errors;
  }

  if (!value) return errors;

  // Type-specific validation
  switch (definition.type) {
    case 'text': {
      const textDef = definition as TextFieldDefinition;
      const textVal = value as TextFieldValue;
      
      if (textDef.maxLength && textVal.content.length > textDef.maxLength) {
        errors.push(`Maximum ${textDef.maxLength} characters allowed`);
      }
      break;
    }

    case 'date': {
      const dateDef = definition as DateFieldDefinition;
      const dateVal = value as DateFieldValue;
      
      if (dateDef.minDate && dateVal.value < dateDef.minDate) {
        errors.push(`Date must be after ${dateDef.minDate}`);
      }
      if (dateDef.maxDate && dateVal.value > dateDef.maxDate) {
        errors.push(`Date must be before ${dateDef.maxDate}`);
      }
      break;
    }

    case 'signature':
    case 'initials': {
      const sigVal = value as SignatureFieldValue | InitialsFieldValue;
      if (!sigVal.imageData && !sigVal.typedText) {
        errors.push('Please provide a signature');
      }
      break;
    }
  }

  return errors;
}

// Validate all fields
export function validateAllFields(fields: Map<string, FieldState>): boolean {
  let allValid = true;
  
  fields.forEach((field) => {
    const errors = validateField(field);
    if (errors.length > 0) {
      allValid = false;
      // Update field errors in store
      useDocumentStore.getState().updateFieldDefinition(
        field.definition.id, 
        { errors } as any
      );
    }
  });
  
  return allValid;
}

Field Coordinates

All field coordinates (x, y, width, height) are normalized values between 0 and 1, representing percentages of the page dimensions. This ensures fields render correctly at any zoom level.

Adding Fields Programmatically

Field Templatestsx
import { nanoid } from 'nanoid';
import { useDocumentStore } from '@mims/sdk-react';

// Field templates for common use cases
export const fieldTemplates = {
  fullName: (page: number, x: number, y: number): TextFieldDefinition => ({
    id: nanoid(),
    type: 'text',
    page,
    x,
    y,
    width: 0.4,
    height: 0.04,
    required: true,
    editable: true,
    label: 'Full Name',
    placeholder: 'Enter your full name',
    maxLength: 100,
    fontSize: 12,
    textAlign: 'left',
  }),

  dateField: (page: number, x: number, y: number): DateFieldDefinition => ({
    id: nanoid(),
    type: 'date',
    page,
    x,
    y,
    width: 0.2,
    height: 0.04,
    required: true,
    editable: true,
    label: 'Date',
    dateFormat: 'MM/DD/YYYY',
  }),

  signature: (page: number, x: number, y: number): SignatureFieldDefinition => ({
    id: nanoid(),
    type: 'signature',
    page,
    x,
    y,
    width: 0.35,
    height: 0.1,
    required: true,
    editable: true,
    label: 'Signature',
    signatureMode: 'draw',
  }),

  checkbox: (page: number, x: number, y: number, label: string): CheckboxFieldDefinition => ({
    id: nanoid(),
    type: 'checkbox',
    page,
    x,
    y,
    width: 0.025,
    height: 0.025,
    required: false,
    editable: true,
    label,
    checkStyle: 'check',
  }),
};

// Usage
function AddFieldsToolbar() {
  const currentPage = useDocumentStore((s) => s.currentPage);
  const addField = useDocumentStore((s) => s.addField);

  return (
    <div className="flex gap-2">
      <button onClick={() => addField(fieldTemplates.fullName(currentPage, 0.1, 0.1))}>
        + Name Field
      </button>
      <button onClick={() => addField(fieldTemplates.dateField(currentPage, 0.1, 0.2))}>
        + Date Field
      </button>
      <button onClick={() => addField(fieldTemplates.signature(currentPage, 0.1, 0.8))}>
        + Signature
      </button>
    </div>
  );
}

Related