Custom field types

Field types are plugins, not a fixed list. Register your own and use its name as type in any definition.

1. Write the plugin

slider.tsx
import { z } from "zod";
import type { FieldRendererProps, ReactFieldPlugin } from "@hardikrastogi/react";

function SliderInput({ props, value, onChange, onBlur, inputId, describedBy, invalid }: FieldRendererProps) {
  const min = typeof props.min === "number" ? props.min : 0;
  const max = typeof props.max === "number" ? props.max : 10;
  const current = typeof value === "number" ? value : min;

  return (
    <input
      id={inputId}
      type="range"
      min={min}
      max={max}
      value={current}
      aria-invalid={invalid || undefined}
      aria-describedby={describedBy}
      onChange={(e) => onChange(Number(e.target.value))}
      onBlur={onBlur}
    />
  );
}

export const sliderPlugin: ReactFieldPlugin = {
  type: "slider",
  schema: z.number(),
  defaultProps: { min: 0, max: 10 },
  emptyValue: 0,
  labelKind: "control",
  Renderer: SliderInput,
};

2. Register it

register
import { FormRenderer, createDefaultRegistry } from "@hardikrastogi/react";

// Create once, outside your component
const registry = createDefaultRegistry();
registry.register(sliderPlugin);

<FormRenderer definition={definition} registry={registry} />

3. Use it in JSON

field
{ "id": "satisfaction", "type": "slider", "label": "How likely are you to recommend us?", "defaultProps": { "min": 0, "max": 10 } }

The playground registers exactly this slider field. Pick the "Custom field" example to see it. (rating, country, currency, url and time all started this way and are now built in — see field types.)

The plugin object

KeyPurpose
typeThe name used in definitions.
schemaA Zod schema describing a valid answer.
defaultPropsDefaults for the field's own defaultProps; the field's values win.
RendererThe React component that draws the input.
labelKindcontrol (label tied to one input, the default), group (label names a group of controls) or inline (your component draws its own label).
emptyValueInitial answer when nothing else is set.
validateOptional. Receives the value and merged props, returns an object with a boolean valid and an errors array. Runs after the built-in rules, only for non-empty answers.

What Renderer receives

PropMeaning
value, onChange, onBlurThe current answer and how to change it.
fieldThe field's config (id, label, required, and so on).
propsPlugin defaults merged with the field's defaultProps.
inputId, labelId, describedByIds to wire up the label, description and error for accessibility.
invalid, disabledCurrent state.
classNamesThe consumer's class name overrides.

Give the element that owns the invalid state aria-invalid. The form uses it to focus the first problem after a failed submit.