Skip to main content

Component Mapping

Create custom UI components for each field type in your forms.

ComponentMap

The ComponentMap object maps field types to React components:

import type { ComponentMap } from "@fogpipe/forma-react";

const components: ComponentMap = {
text: TextInput,
email: EmailInput,
number: NumberInput,
integer: IntegerInput,
boolean: Checkbox,
select: SelectDropdown,
multiselect: MultiSelect,
date: DatePicker,
textarea: TextArea,
// ... add more as needed
};

Component Props

All components receive { field, spec } props. The field object contains everything needed to render and interact with the field.

Common Field Properties

PropertyTypeDescription
namestringField path/name
labelstringDisplay label
descriptionstring | undefinedHelp text
placeholderstringInput placeholder
requiredbooleanWhether field is required
enabledbooleanWhether field is editable
readonlybooleanWhether field is readonly (visible, value submitted)
visiblebooleanWhether field is visible
valueunknownCurrent field value
errorsFieldError[]Validation errors
prefixstring | undefinedPrefix adorner text (e.g., "$") — adornable types only
suffixstring | undefinedSuffix adorner text (e.g., "kg") — adornable types only
variantstring | undefinedPresentation variant hint (e.g., "slider", "nps")
variantConfigRecord<string, unknown>Variant-specific configuration

Field Methods

MethodDescription
onChange(value)Update field value
onBlur()Mark field as touched

Accessibility Attributes

PropertyDescription
aria-invalidSet when field has errors
aria-describedbyID of description/error element
aria-requiredSet when field is required

Note: These ARIA attributes are available via getFieldProps() but are not present on the typed component props (TextComponentProps, etc.). Components can derive them from errors and required.

Field Type Components

Text Input

import type { TextComponentProps } from "@fogpipe/forma-react";

const TextInput = ({ field }: TextComponentProps) => (
<div>
<label htmlFor={field.name}>
{field.label}
{field.required && <span className="required">*</span>}
</label>
<input
id={field.name}
type="text"
value={field.value || ""}
onChange={(e) => field.onChange(e.target.value)}
onBlur={field.onBlur}
placeholder={field.placeholder}
disabled={!field.enabled}
aria-invalid={field["aria-invalid"]}
aria-describedby={field["aria-describedby"]}
aria-required={field["aria-required"]}
/>
{field.description && (
<p id={`${field.name}-description`}>{field.description}</p>
)}
{field.errors.map((error, i) => (
<p key={i} className="error" role="alert">
{error.message}
</p>
))}
</div>
);

Number Input

import type { NumberComponentProps } from "@fogpipe/forma-react";

const NumberInput = ({ field }: NumberComponentProps) => (
<div>
<label htmlFor={field.name}>{field.label}</label>
<input
id={field.name}
type="number"
value={field.value ?? ""}
onChange={(e) => {
const value = e.target.value === "" ? null : Number(e.target.value);
field.onChange(value);
}}
onBlur={field.onBlur}
disabled={!field.enabled}
/>
</div>
);

Boolean (Checkbox)

import type { BooleanComponentProps } from "@fogpipe/forma-react";

const Checkbox = ({ field }: BooleanComponentProps) => (
<div>
<label>
<input
type="checkbox"
checked={field.value || false}
onChange={(e) => field.onChange(e.target.checked)}
disabled={!field.enabled}
/>
<span>{field.label}</span>
</label>
</div>
);

Select Dropdown

import type { SelectComponentProps } from "@fogpipe/forma-react";

const SelectDropdown = ({ field }: SelectComponentProps) => (
<div>
<label htmlFor={field.name}>{field.label}</label>
<select
id={field.name}
value={field.value || ""}
onChange={(e) => field.onChange(e.target.value)}
onBlur={field.onBlur}
disabled={!field.enabled}
>
<option value="">Select...</option>
{field.options?.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
);

Multi-Select

import type { MultiSelectComponentProps } from "@fogpipe/forma-react";

const MultiSelect = ({ field }: MultiSelectComponentProps) => {
const values = (field.value as string[]) || [];

const toggleValue = (optionValue: string) => {
const newValues = values.includes(optionValue)
? values.filter((v) => v !== optionValue)
: [...values, optionValue];
field.onChange(newValues);
};

return (
<div>
<label>{field.label}</label>
<div>
{field.options?.map((option) => (
<label key={option.value}>
<input
type="checkbox"
checked={values.includes(String(option.value))}
onChange={() => toggleValue(String(option.value))}
disabled={!field.enabled}
/>
{option.label}
</label>
))}
</div>
</div>
);
};

Date Picker

import type { DateComponentProps } from "@fogpipe/forma-react";

const DatePicker = ({ field }: DateComponentProps) => (
<div>
<label htmlFor={field.name}>{field.label}</label>
<input
id={field.name}
type="date"
value={field.value || ""}
onChange={(e) => field.onChange(e.target.value)}
onBlur={field.onBlur}
disabled={!field.enabled}
/>
</div>
);

Textarea

import type { TextComponentProps } from "@fogpipe/forma-react";

const TextArea = ({ field }: TextComponentProps) => (
<div>
<label htmlFor={field.name}>{field.label}</label>
<textarea
id={field.name}
value={field.value || ""}
onChange={(e) => field.onChange(e.target.value)}
onBlur={field.onBlur}
placeholder={field.placeholder}
disabled={!field.enabled}
rows={4}
/>
</div>
);

Type-Specific Props

Different field types have specific prop types:

TypeProps TypeExtra Properties
text, email, urlTextComponentProps-
numberNumberComponentPropsmin, max, step
integerIntegerComponentPropsmin, max, step
booleanBooleanComponentProps-
selectSelectComponentPropsoptions
multiselectMultiSelectComponentPropsoptions
dateDateComponentProps-
datetimeDateTimeComponentProps-
arrayArrayComponentPropsitemFields, helpers, minItems, maxItems
computedComputedComponentPropsexpression
displayDisplayComponentPropscontent, sourceValue, format

Options Property

For select and multiselect fields, field.options contains the available choices:

interface SelectOption {
value: string | number;
label: string;
}

Options are pre-filtered by visibleWhen FEEL expressions — your component only receives visible options. See Options Visibility for details.

Styling Best Practices

  1. Use CSS classes - Apply consistent styling through class names
  2. Handle all states - Style disabled, error, and focused states
  3. Show required indicators - Mark required fields clearly
  4. Display errors prominently - Make validation errors visible
  5. Support dark mode - Use CSS variables or theme context

Display Field

Display fields show read-only content. They receive DisplayComponentProps instead of the standard field props — notably, they have no value or onChange. See Display Fields for the full guide.

import type { DisplayComponentProps } from "@fogpipe/forma-react";

function DisplayField({ field }: DisplayComponentProps) {
if (!field.visible) return null;

const text =
field.sourceValue != null ? String(field.sourceValue) : field.content;

return (
<div>
{field.label && <p className="font-medium">{field.label}</p>}
{text && <p>{text}</p>}
</div>
);
}

Complete ComponentMap Example

const components: ComponentMap = {
text: TextInput,
email: TextInput,
url: TextInput,
password: PasswordInput,
number: NumberInput,
integer: NumberInput,
boolean: Checkbox,
select: SelectDropdown,
multiselect: MultiSelect,
date: DatePicker,
datetime: DateTimePicker,
textarea: TextArea,
array: ArrayField,
object: ObjectField,
computed: ComputedDisplay,
display: DisplayField,
};