Skip to content

Canonical Field Contracts

This document defines the canonical contract for each of the 12 Tier 1 field types. Every adapter package must conform to these contracts. The contracts specify value types, empty semantics, serialization, and rendering behavior that all adapters share.

General Rules

  • All field components receive IFieldProps<T> via React.cloneElement().
  • Adapters must convert UI-library-specific types at the component boundary, never storing library-specific values in form state.
  • The value prop comes from react-hook-form and may be undefined, null, or the typed value.
  • The setFieldValue(fieldName, value, skipSave?, timeout?) callback writes values back to form state.
  • All fields must support both editable and readOnly rendering modes.

Textbox

PropertyValue
Type key"Textbox"
Value typestring
Empty valueundefined, null, or ""
Default valueundefined (renders as "" via ?? "" coercion)
SerializationPlain string
Uses optionsNo
Config shape{ ellipsifyTextCharacters?: number; placeHolder?: string; multiline?: boolean }
ReadOnly displayReadOnlyText -- renders the string, optionally truncated via ellipsifyTextCharacters
Save debounce3000ms

Null/undefined/empty string behavior:

  • null and undefined both render as empty input (?? "")
  • "" is treated as empty for required validation
  • All three are functionally equivalent for display purposes

Number

PropertyValue
Type key"Number"
Value typenumber
Empty valueundefined, null
Default valueundefined (renders as "" via isNull() check)
SerializationJavaScript number (not string)
Uses optionsNo
Config shape{} (no custom config)
ReadOnly displayReadOnlyText with String(value)
Save debounce1500ms

Null/undefined/empty string behavior:

  • null and undefined render as empty input
  • 0 is a valid value, not empty
  • Input is parsed via Number() and rejected if isNaN
  • Adapters must call setFieldValue with a number, not a string

Toggle

PropertyValue
Type key"Toggle"
Value typeboolean
Empty valueundefined, null, false
Default valueundefined (renders as unchecked via !!value)
SerializationBoolean (true / false)
Uses optionsNo
Config shape{} (no custom config)
ReadOnly displayReadOnlyText with convertBooleanToYesOrNoText(value) -- shows "Yes" or "No"
Save debounceNone (immediate)

Null/undefined/empty string behavior:

  • null, undefined, and false all render as unchecked
  • Only true renders as checked
  • The !!value coercion handles all falsy values uniformly

PropertyValue
Type key"Dropdown"
Value typestring
Empty valueundefined, null, ""
Default valueundefined (renders as "" placeholder option)
SerializationString (the value property from the selected IOption)
Uses optionsYes -- IOption[] via options prop
Config shape{ placeHolder?: string; setDefaultKeyIfOnlyOneOption?: boolean }
ReadOnly displayReadOnlyText with the raw value string
Save debounceNone (immediate)

Null/undefined/empty string behavior:

  • null and undefined coerce to "" via ?? ""
  • "" selects the placeholder option
  • When setDefaultKeyIfOnlyOneOption is true and exactly one option exists, auto-selects it on mount

Options contract:

  • Options are IOption[] with { value: string | number; label: string; disabled?: boolean; group?: string }
  • Option values are coerced to strings via String(option.value) for <option> elements
  • Selected value comparison uses string equality

Multiselect

PropertyValue
Type key"Multiselect"
Value typestring[]
Empty valueundefined, null, []
Default valueundefined (coerced to [] via ?? [])
SerializationArray of strings
Uses optionsYes -- IOption[] via options prop
Config shape{} (no custom config)
ReadOnly display<ul> list of selected values, or "-" if empty
Save debounce1500ms

Null/undefined/empty string behavior:

  • null and undefined coerce to []
  • [] (empty array) is treated as empty for required validation
  • Values are always stored as string[]

DateControl

PropertyValue
Type key"DateControl"
Value typestring (ISO 8601 UTC)
Empty valueundefined, null
Default valueundefined (renders as empty date picker)
SerializationISO 8601 string (e.g., "2024-01-15T00:00:00.000Z")
Uses optionsNo
Config shape{} (no custom config)
ReadOnly display<time> element with formatDateTime(value, { hideTimestamp: true }) or "-" if null
Save debounceNone (immediate)

Null/undefined/empty string behavior:

  • null means "no date selected" -- this is the canonical empty value
  • undefined also renders as empty
  • Clearing a date calls setFieldValue(fieldName, null)
  • Adapters must convert their date library objects to ISO strings at the boundary (see Date Policy)

Adapter boundary rule:

  • All adapters store ISO 8601 strings in form state
  • Adapter-specific date objects (dayjs, Date, etc.) are created at render time and converted back to ISO on change

Slider

PropertyValue
Type key"Slider"
Value typenumber
Empty valueundefined, null
Default valueundefined (renders as 0 via ?? 0)
SerializationJavaScript number
Uses optionsNo
Config shape{ max?: number; min?: number; step?: number }
ReadOnly displayReadOnlyText with String(value)
Save debounceNone (immediate)

Null/undefined/empty string behavior:

  • null and undefined coerce to 0 for the range input
  • 0 is a valid value, not considered empty (unless min > 0)

RadioGroup

PropertyValue
Type key"RadioGroup"
Value typestring
Empty valueundefined, null
Default valueundefined (no radio selected)
SerializationString (the value property from the selected IOption)
Uses optionsYes -- IOption[] via options prop
Config shape{} (no custom config)
ReadOnly displayReadOnlyText with the matching option label, or the raw value if no label match
Save debounceNone (immediate)

Null/undefined/empty string behavior:

  • null and undefined result in no radio being selected
  • Comparison uses String(value) === String(option.value) for loose matching

CheckboxGroup

PropertyValue
Type key"CheckboxGroup"
Value typestring[]
Empty valueundefined, null, []
Default valueundefined (coerced to [] via Array.isArray check)
SerializationArray of strings
Uses optionsYes -- IOption[] via options prop
Config shape{} (no custom config)
ReadOnly displayReadOnlyText with comma-separated matching option labels
Save debounceNone (immediate)

Null/undefined/empty string behavior:

  • null and undefined are coerced to [] via Array.isArray(value) ? value : []
  • [] is treated as empty for required validation
  • On change, checked values are added and unchecked values are filtered out

Textarea

PropertyValue
Type key"Textarea"
Value typestring
Empty valueundefined, null, ""
Default valueundefined (renders as "")
SerializationPlain string
Uses optionsNo
Config shape{ autoAdjustHeight?: boolean; numberOfRows?: number; ellipsifyTextCharacters?: number; additionalInfo?: string; maxLimit?: number; saveCallback?: () => void; renderExtraModalFooter?: () => ReactNode }
ReadOnly displayReadOnlyText with optional truncation via ellipsifyTextCharacters
Save debounce3000ms

Null/undefined/empty string behavior:

  • null and undefined render as "" via template literal coercion
  • Supports an expanded modal editor for long-form text

Note: In the fluent adapter, Textarea maps to PopOutEditor (a rich textarea with expand-to-modal). In headless, mui, antd, chakra, and mantine adapters it maps to Textarea.


ReadOnly

PropertyValue
Type key"ReadOnly"
Value typestring (display only)
Empty valueundefined, null, ""
Default valueundefined
SerializationPlain string (never written back to form state)
Uses optionsNo
Config shapeIReadOnlyFieldProps -- adapter-specific config for display formatting
ReadOnly displayAlways read-only: ReadOnlyText component
Save debounceN/A (never saves)

Null/undefined/empty string behavior:

  • All empty values render as "-" or empty string depending on ReadOnlyText implementation
  • This field type is always read-only regardless of the readOnly prop

DynamicFragment

PropertyValue
Type key"DynamicFragment"
Value typestring
Empty valueundefined, null
Default valueundefined
SerializationPlain string (stored in hidden input)
Uses optionsNo
Config shape{} (no custom config)
ReadOnly displaySame as editable: <input type="hidden"> (invisible in both modes)
Save debounceN/A (value set programmatically)

Null/undefined/empty string behavior:

  • Value is stored in a hidden input and not visible to the user
  • Used for computed values, parent references, and metadata fields
  • Renders as <input type="hidden"> in all adapters

Summary Table

FieldValue TypeEmpty ValueUses OptionsSave Debounce
Textboxstringundefined / null / ""No3000ms
Numbernumberundefined / nullNo1500ms
Togglebooleanundefined / null / falseNoImmediate
Dropdownstringundefined / null / ""IOption[]Immediate

| Multiselect | string[] | undefined / null / [] | IOption[] | 1500ms | | DateControl | string (ISO) | undefined / null | No | Immediate | | Slider | number | undefined / null | No | Immediate | | RadioGroup | string | undefined / null | IOption[] | Immediate | | CheckboxGroup | string[] | undefined / null / [] | IOption[] | Immediate | | Textarea | string | undefined / null / "" | No | 3000ms | | ReadOnly | string | undefined / null / "" | No | N/A | | DynamicFragment | string | undefined / null | No | N/A |


ReadOnly Contract

Every adapter must conform to these rules when rendering a field in readOnly mode. These rules are non-negotiable for Tier 1 parity.

1. Universal readOnly support

Every Tier 1 field must support readOnly mode. There are no exceptions. When props.readOnly is true, the field must render display-only content.

2. No interactive elements in readOnly output

ReadOnly rendering must not produce <input>, <select>, or <textarea> elements in the DOM output. The only exception is DynamicFragment, which renders <input type="hidden"> (invisible, non-interactive).

3. Empty display sentinel

All fields must render "-" (a single hyphen) as the display text for missing, null, or undefined values. This applies uniformly across all adapters and all field types.

4. Text and Number fields

  • Textbox: Renders the string value via ReadOnlyText, or "-" if the value is null/undefined/empty string.
  • Number: Renders String(value) via ReadOnlyText, or "-" if the value is null/undefined.

5. Boolean fields (Toggle)

Renders "Yes" or "No" via convertBooleanToYesOrNoText(). An undefined/null value renders as "" (empty string from the conversion function).

6. Single-select fields (Dropdown, RadioGroup)

Renders the selected option label (not the option value), or "-" if no option is selected. For RadioGroup, the adapter looks up the matching option label via options.find(). For Dropdown, the label lookup is performed via options.find() before passing to ReadOnlyText.

7. Multi-select fields (MultiSelect, CheckboxGroup)

Renders comma-separated option labels for all selected values, or "-" if the selection is empty. CheckboxGroup uses options.filter().map(o => o.label).join(", "). MultiSelect in some adapters renders a <ul> list of selected values.

8. Date fields (DateControl)

Renders formatDateTime(value, { hideTimestamp: true }) output for non-null values, or "-" if the value is null/undefined. Some adapters (headless) use a <time> element with a dateTime attribute for semantic markup.

9. DynamicFragment

Always renders <input type="hidden"> regardless of readOnly state. This is the only field type that produces an <input> element in readOnly mode. The hidden input is invisible and non-interactive.

10. ReadOnly field type

The ReadOnly field type is always in read-only mode regardless of the readOnly prop. It delegates directly to the adapter's ReadOnlyText component.

Released under the MIT License.