Custom Widgets and Fields
The API allows to specify your own custom widget and field components:
- A widget represents a HTML tag for the user to enter data, eg.
input,select, etc. - A field usually wraps one or more widgets and most often handles internal field state; think of a field as a form row, including the labels.
Customizing the default fields and widgets
You can override any default field and widget, including the internal widgets like the CheckboxWidget that BooleanField
renders for boolean values. You can override any field and widget just by providing the customized fields/widgets in the
fields and widgets props:
import { RJSFSchema, UiSchema, WidgetProps, RegistryWidgetsType } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8';
const schema: RJSFSchema = {
type: 'boolean',
default: true,
};
const uiSchema: UiSchema = {
'ui:widget': 'checkbox',
};
const CustomCheckbox = function (props: WidgetProps) {
return (
<button id='custom' className={props.value ? 'checked' : 'unchecked'} onClick={() => props.onChange(!props.value)}>
{String(props.value)}
</button>
);
};
const widgets: RegistryWidgetsType = {
CheckboxWidget: CustomCheckbox,
};
render(
<Form schema={schema} uiSchema={uiSchema} validator={validator} widgets={widgets} />,
document.getElementById('app'),
);
This allows you to create a reusable customized form class with your custom fields and widgets:
import { RegistryFieldsType, RegistryWidgetsType } from '@rjsf/utils';
import { FormProps } from '@rjsf/core';
const customFields: RegistryFieldsType = { StringField: CustomString };
const customWidgets: RegistryWidgetsType = { CheckboxWidget: CustomCheckbox };
function MyForm(props: FormProps) {
return <Form fields={customFields} widgets={customWidgets} {...props} />;
}
The default fields you can override are:
ArrayFieldArraySchemaFieldBooleanFieldDescriptionFieldOneOfFieldAnyOfFieldLayoutGridFieldLayoutMultiSchemaFieldLayoutHeaderFieldNullFieldNumberFieldObjectFieldSchemaFieldStringFieldTitleFieldUnsupportedFieldFallbackField
The default widgets you can override are:
AltDateTimeWidgetAltDateWidgetCheckboxesWidgetCheckboxWidgetColorWidgetDateTimeWidgetDateWidgetEmailWidgetFileWidgetHiddenWidgetPasswordWidgetRadioWidgetRangeWidgetSelectWidgetTextareaWidgetTextWidgetTimeWidgetUpDownWidgetURLWidget
Raising errors from within a custom widget or field
You can raise custom 'live validation' errors by overriding the onChange method to provide feedback while users are actively changing the form data.
If you do set errors this way, you must also clear them this way by passing undefined to the onChange() for the errorSchema parameter.
While these errors are retained during validation, it is still preferred for you to use the customValidate Form prop mechanism instead.
import { ErrorSchema, RJSFSchema, UiSchema, WidgetProps, RegistryWidgetsType } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8';
const schema: RJSFSchema = {
type: 'text',
default: 'hello',
};
const uiSchema: UiSchema = {
'ui:widget': 'text',
};
const CustomTextWidget = function (props: WidgetProps) {
const { id, value } = props;
const raiseErrorOnChange = ({ target: { value } }: ChangeEvent<HTMLInputElement>) => {
let raiseError: ErrorSchema | undefined;
if (value !== 'test') {
raiseError = {
__errors: ['Value must be "test"'],
};
}
props.onChange(value, [], raiseError, id);
};
return <input id={id} onChange={raiseErrorOnChange} value={value || ''} />;
};
const widgets: RegistryWidgetsType = {
TextWidget: CustomTextWidget,
};
render(
<Form schema={schema} uiSchema={uiSchema} validator={validator} widgets={widgets} />,
document.getElementById('app'),
);
This creates a custom text widget that raises an error if the input value does not match 'test'.
Adding your own custom widgets
You can provide your own custom widgets to a uiSchema for the following json data types:
stringnumberintegerbooleanarray
import { RJSFSchema, UiSchema, WidgetProps } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8';
const schema: Schema = {
type: 'string',
};
const uiSchema: UiSchema = {
'ui:widget': (props: WidgetProps) => {
return (
<input
type='text'
className='custom'
value={props.value}
required={props.required}
onChange={(event) => props.onChange(event.target.value)}
/>
);
},
};
render(<Form schema={schema} uiSchema={uiSchema} validator={validator} />, document.getElementById('app'));
The following props are passed to custom widget components:
id: The generated id for this widget, used to provide uniquenames andids for the HTML field elements rendered by widgets;name: The unique name of the field, usually derived from the name of the property in the JSONSchema; Provided in support of custom widgets;schema: The JSONSchema subschema object for this widget;uiSchema: The uiSchema for this widget;value: The current value for this widget;placeholder: The placeholder for the widget, if any;required: The required status of this widget;disabled: A boolean value stating if the widget is disabled;readonly: A boolean value stating if the widget is read-only;autofocus: A boolean value stating if the widget should autofocus;label: The computed label for this widget, as a stringhideLabel: A boolean value, if true, will cause the label to be hidden. This is useful for nested fields where you don't want to clutter the UI. Customized vialabelin theUiSchema;multiple: A boolean value stating if the widget can accept multiple values;onChange: The value change event handler; call it with the new value every time it changes;onKeyChange: The key change event handler (only called for fields withadditionalProperties); pass the new value every time it changes;onBlur: The input blur event handler; call it with the widget id and value;onFocus: The input focus event handler; call it with the widget id and value;options: A map of options passed as a prop to the component (see Custom widget options).options.enumOptions: For enum fields, this property contains the list of options for the enum as an array of { label, value } objects. If the enum is defined using the oneOf/anyOf syntax, the entire schema object for each option is appended onto the { schema, label, value } object.rawErrors: An array of strings listing all generated error messages from encountered errors for this widget.registry: A registry object (read next).
Custom component registration
Alternatively, you can register them all at once by passing the widgets prop to the Form component, and reference their identifier from the uiSchema:
import { RJSFSchema, UiSchema, WidgetProps, RegistryWidgetsType } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8';
const MyCustomWidget = (props: WidgetProps) => {
return (
<input
type='text'
className='custom'
value={props.value}
required={props.required}
onChange={(event) => props.onChange(event.target.value)}
/>
);
};
const widgets: RegistryWidgetsType = {
myCustomWidget: MyCustomWidget,
};
const schema: RJSFSchema = {
type: 'string',
};
const uiSchema: UiSchema = {
'ui:widget': 'myCustomWidget',
};
render(
<Form schema={schema} uiSchema={uiSchema} validator={validator} widgets={widgets} />,
document.getElementById('app'),
);
This is useful if you expose the uiSchema as pure JSON, which can't carry functions.
Custom widget options
If you need to pass options to your custom widget, you can add a ui:options object containing those properties. If the widget has defaultProps, the options will be merged with the (optional) options object from defaultProps:
import { RJSFSchema, UiSchema, WidgetProps } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8';
const schema: RJSFSchema = {
type: 'string',
};
function MyCustomWidget(props: WidgetProps) {
const { options } = props;
const { color, backgroundColor } = options;
return <input style={{ color, backgroundColor }} />;
}
MyCustomWidget.defaultProps = {
options: {
color: 'red',
},
};
const uiSchema: UiSchema = {
'ui:widget': MyCustomWidget,
'ui:options': {
backgroundColor: 'yellow',
},
};
// renders red on yellow input
render(<Form schema={schema} uiSchema={uiSchema} validator={validator} />, document.getElementById('app'));
Note: This also applies to registered custom components.