npm package: @nolwenture/formik-jsonschema-form
Description: Formik form generation from json schema
Author: anttivikman
Latest version: 2.0.2

# formik-jsonschema-form

## Installation

With npm:

```text
npm install @nolwenture/formik-jsonschema-form
```

With yarn:

```text
yarn add @nolwenture/formik-jsonschema-form
```

## Testing

Run unit tests:

```text
npm run test
```

To run storybook tests make sure you have Playwright installed:

```text
npx playwright install
```

Run storybook tests:

```text
npm run test-storybook
```

## Usage

### Basic usage

Pass your schema to the `Form` component, which should be passed as a child to the `Formik` component, for example:

```tsx
import { Form } from "@nolwenture/formik-jsonschema-form";
import { Formik } from "formik";

const schema = {
  title: "My form",
  type: "object",
  properties: {
    firstName: { type: "string", title: "First name" },
    lastName: { type: "string", title: "Last name" },
  },
};

const uiSchema = {
  "ui:description": "Form description",
  firstName: { "ui:description": "Field description" },
};

export function MyFormComponent() {
  return (
    <Formik
      initialValues={{ firstName: "", lastName: "" }}
      onSubmit={handleSubmit}
    >
      <Form schema={schema} uiSchema={uiSchema}></Form>
    </Formik>
  );
}
```

Pass your uiSchema to the `Form`component, which should be passed as a child to the `Formik`component, just like schema. uiSchema will tell the form more about its own layout and styles.

Basic available elements to add to the form or a field of the form via uiSchema are:

- "ui:title"
- "ui:description"
- "ui:help"
- "ui:options"
- "ui:info"

Most of those elements take a string for value, but `ui:info` takes an object with the possible keys of

- `label` for the clickable anchor (if not provided, default value is "i")
- `content` for the content of the popup container, and
- `position` taking a string of either "top", "bottom", "left" or "right" (if not provided, default value is "top")

All elements of `ui:info` are customisable. The styledComponent is called `FieldInfo` and it consists of the following elements:

- FieldInfoContainer
- FieldInfoBoxElement
- FieldInfoBoxInnerElement
- FieldInfoBoxArrowElement
- FieldInfoLabel

```tsx
import { Form } from "@nolwenture/formik-jsonschema-form";
import { Formik } from "formik";

const schema = {
  title: "My form",
  type: "object",
  properties: {
    firstName: { type: "string", title: "First name" },
    lastName: { type: "string", title: "Last name" },
  },
};

const uiSchema = {
  "ui:description": "Form description",
  firstName: { "ui:description": "Field description" },
  lastName: {
    "ui:info": {
      label: "?",
      content: "Add your family name",
    },
  },
};

export function MyFormComponent() {
  return (
    <Formik
      initialValues={{ firstName: "", lastName: "" }}
      onSubmit={handleSubmit}
    >
      <Form schema={schema} uiSchema={uiSchema}></Form>
    </Formik>
  );
}
```

### Using create-functions for initialValues and validationSchema

This library offers create-functions that, based on the JSON schema provided, will return a basic object of either initialValues or yup validationSchema.

```tsx
import { Form } from "@nolwenture/formik-jsonschema-form";
import {
  createInitialValues,
  createValidationSchema,
} from "@nolwenture/formik-jsonschema-form/dist/utils";
import { Formik } from "formik";

const schema = {
  title: "My form",
  type: "object",
  properties: {
    firstName: { type: "string", title: "First name" },
    lastName: { type: "string", title: "Last name" },
  },
};

const uiSchema = {
  "ui:description": "Form description",
  firstName: { "ui:description": "Field description" },
  lastName: {
    "ui:info": {
      label: "?",
      content: "Add your family name",
    },
  },
};

export function MyFormComponent() {
  return (
    <Formik
      initialValues={createInitialValues(schema)}
      validationSchema={createValidationSchema({schema})}
      onSubmit={handleSubmit}
    >
      <Form schema={schema} uiSchema={uiSchema}></Form>
    </Formik>
  );
}
```

### Validation

The `createValidationSchema` function generates a Yup validation schema from your JSON Schema. It supports:

#### Supported JSON Schema Keywords

**String validation:**
- `minLength` - Minimum string length
- `maxLength` - Maximum string length
- `pattern` - Regular expression pattern

**Number validation:**
- `minimum` - Minimum value (inclusive)
- `maximum` - Maximum value (inclusive)
- `exclusiveMinimum` - Minimum value (exclusive)
- `exclusiveMaximum` - Maximum value (exclusive)
- `multipleOf` - Value must be a multiple of this number

**Array validation:**
- `minItems` - Minimum number of items
- `maxItems` - Maximum number of items
- `uniqueItems` - All items must be unique

**Equality validation:**
- `const` - Value must equal the specified constant
- `enum` - Value must be one of the specified values
- `not: { const: ... }` - Value must NOT equal the specified constant

**Required fields:**
- Fields listed in the `required` array are validated as required

#### Function Signature

```tsx
createValidationSchema(
  { schema, uiSchema },  // Schema and optional UI schema
  {                      // Optional options object
    localize,            // Localization function i18next t compatible
    definitions,         // Use these definitions instead of definitions in schema
  }
)
```

#### Custom Error Messages

You can customize error messages using `ui:validation` in the uiSchema:

```tsx
const uiSchema = {
  username: {
    "ui:validation": {
      error: {
        minLength: "Username must be at least 3 characters",
        pattern: "Username can only contain letters and numbers",
        required: "Please enter a username",
      },
    },
  },
};
```

#### Localization

Pass a `localize` function compatible with i18next to get localized error messages:

```tsx
import { useTranslation } from 'react-i18next';

const { t } = useTranslation();
const validationSchema = createValidationSchema(
  { schema, uiSchema },
  { localize: t }
);
```

The function will be called with keys like `validation.error.minLength`, `validation.error.required`, etc.

#### Error Message Priority

1. **Custom UI message** - `uiSchema["ui:validation"].error[keyword]`
2. **Localized message** - Result from `localize("validation.error.{keyword}", params)`
3. **Default message** - Built-in English messages

#### Example

```tsx
const schema = {
  type: "object",
  required: ["email", "age"],
  properties: {
    email: {
      type: "string",
      pattern: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
    },
    age: {
      type: "number",
      minimum: 18,
      maximum: 120,
    },
    tags: {
      type: "array",
      minItems: 1,
      maxItems: 5,
      uniqueItems: true,
      items: { type: "string" },
    },
    status: {
      type: "string",
      const: "active",
    },
  },
};

const uiSchema = {
  email: {
    "ui:validation": {
      error: {
        pattern: "Please enter a valid email address",
      },
    },
  },
};

const validationSchema = createValidationSchema({ schema, uiSchema });
```

#### Password Confirmation (Cross-Field Validation)

Use `const: { $data: "1/fieldName" }` to validate that one field equals another. The `1/` prefix uses JSON Pointer syntax to reference a sibling field (go up 1 level):

```tsx
const schema = {
  type: "object",
  required: ["password", "confirmPassword"],
  properties: {
    password: {
      type: "string",
      title: "Password",
      minLength: 8,
    },
    confirmPassword: {
      type: "string",
      title: "Confirm password",
      const: { $data: "1/password" },  // Must match password field
    },
  },
};

const uiSchema = {
  confirmPassword: {
    "ui:validation": {
      error: {
        const: "Passwords must match",
      },
    },
  },
};

const validationSchema = createValidationSchema({ schema, uiSchema });
```

The `$data` reference syntax follows the AJV validation library pattern with JSON Pointer paths. This is only supported for `const` keyword at the moment, but it can be extended to support other keywords in the future (e.g. minimum to compare endDate is after startDate).


### Using custom components

Form components (such as wrappers) can be overridden with custom ones

Here is an example of overriding `FieldWrapper` (wrapper used on all fields):

```tsx
function CustomFieldWrapper({ children }) {
  return (
    <div
      style={{
        backgroundColor: "lightgrey",
        boxShadow: "1px 1px 5px gray",
        margin: "10px",
        padding: "10px",
      }}
    >
      {children}
    </div>
  );
}

...

return (
  <Formik initialValues={initialValues} onSubmit={handleOnSubmit}>
    <Form
      schema={schema}
      uiSchema={uiSchema}
      components={{
        FieldWrapper: CustomFieldWrapper,
      }}
    ></Form>
  </Formik>
);
```

And another example how to override `FieldInfo`:

```tsx
const CustomFieldInfo = styled(FieldInfo)`
  outline: 2px red solid;
  margin: 0.5em;
  ${FieldInfo.styles.FieldInfoBoxInnerElementStyle} {
    min-width: 200px;
    background: orange;
    color: blue;
  }
  ${FieldInfo.styles.FieldInfoLabelStyle} {
    color: green;
  }
`;

...

return (
  <Formik initialValues={initialValues} onSubmit={handleOnSubmit}>
    <Form
      schema={schema}
      uiSchema={uiSchema}
      components={{
        FieldInfo: CustomFieldInfo,
      }}
    ></Form>
  </Formik>
);
```

Custom fields can be used in addition to all the default fields:

```tsx
import { Form, FormRegistry } from "@nolwenture/formik-jsonschema-form";
import { useContext } from "react";

const schema = {
  title: "My form",
  type: "object",
  properties: {
    firstField: { title: "First field" },
  },
};

function MyCustomFieldComponent(props) {
  const {
    templates: { FieldTemplate },
  } = useContext(FormRegistry);

  return (
    <FieldTemplate {...props}>
      <p>My awesome field!</p>
    </FieldTemplate>
  );
}

const fields = { myField: MyCustomFieldComponent };

// Specify the fields that should use your custom field component in uiSchema
const uiSchema = {
  firstField = { "ui:field": "myField" },
};

...

return (
  <Formik initialValues={initialValues} onSubmit={handleOnSubmit}>
    <Form schema={schema} uiSchema={uiSchema} fields={fields}></Form>
  </Formik>
);
```

The style for buttons to add and remove (and move) array or object items can be overridden within the uiSchema by giving inline style.

The buttons that can be customized that way are by defining these under "ui:options":

- "addButton"
- "removeButton"
- "upButton"
- "downButton"
- "customButton"

`content` takes a ReactNode as input.

```tsx

const schema = {
  title: "Form with customized buttons",
  type: "object",
  properties: {
    someArray: {
      type: "array",
      title: "Some array",
      description: "Array with customized buttons",
      additionalItems: {
        type: "string",
      },
    },
  }
}

const uiSchema = {
  "ui:description": "Form description",
  // make a green ADD button for someArray that reads "Hello!"
  someArray: {
    "ui:options": {
      addButton: {
        content: <b>Hello!</b>,
        props: { style: { background: "green" } },
      },
    },
  }
};

...

return (
  <Formik initialValues={initialValues} onSubmit={handleOnSubmit}>
    <Form
      schema={schema}
      uiSchema={uiSchema}
    ></Form>
  </Formik>
);
```

### Required input

Required input can be displayed with an asterisk next to the title.
The respective fieldname needs to be given inside the required-array of the object holding the field in question in the schema.
In the example, required fields are 'lastName', 'favouriteFood' inside the arrayOfObject, and 'tasks' in nestedObject.

The styling of the asterisk can be customised by applying style to styledComponents.RequiredIndicator.

```tsx
import { Form } from "@nolwenture/formik-jsonschema-form";
import { Formik } from "formik";

const CustomizedRequiredIndicator = styled(styledComponents.RequiredIndicator)`
  color: aqua;
`;

const schema = {
  title: "My Form",
  required: ["lastName"],
  properties: {
    firstName: { type: "string", title: "First name" },
    lastName: { type: "string", title: "Last name" },
    arrayOfObject: {
      type: "array",
      title: "Array of objects",
      items: {
        type: "object",
        title: "Favourites",
        required: ["favouriteFood"],
        properties: {
          favouriteFood: { type: "string", title: "Favourite food" },
          favouriteSong: { type: "string", title: "Favourite song" },
        },
      },
    },
    nestedObject: {
      type: "object",
      title: "Nested Object",
      required: ["tasks"],
      properties: {
        hobbies: { type: "string", title: "Hobbies" },
        tasks: { type: "string", title: "Tasks" },
      },
    },
  },
};

const uiSchema = {
  "ui:description": "Form description",
  firstName: { "ui:description": "Field description" },
};

export function MyFormComponent() {
  return (
    <Formik
      initialValues={{ firstName: "", lastName: "" }}
      onSubmit={handleSubmit}
    >
      <Form
        schema={schema}
        uiSchema={uiSchema}
        components={{ RequiredIndicator: CustomizedRequiredIndicator }}
      ></Form>
    </Formik>
  );
}
```

### Enum fields

Enum fields are fields with multiple options to either choose `oneOf` or `anyOf` from.
There is an alternative field implementation for "anyOf" that when given `"ui:field": "SearchableMultiTagSelectField"` results in a searchable multi tag select field.

A searchable single select field is also available for `oneOf` and `enum`. To use it, add the following to the field's uiSchema: `"ui:field": "SearchableSelectField"`

The type of the field holding the enum is defined by its return value. `oneOf` enums will only return a single value and therefore, the type of the returned value is used as the field type (in the following example, "number" is the correct field type).

`anyOf`enums will return the value(s) inside an array, even if only one option was selected. Therefore, the correct field type for the field holding the `anyOf`-enum is "array".

```tsx
const schema = {
  properties: {
    enum: {
      type: "number",
      title: "Enum",
      enum: [1, 2, 3, 4],
    },
    enumOneOf: {
      type: "number",
      title: "Enum oneOf (non-standard)",
      oneOf: [
        { const: 1, title: "one" },
        { const: 2, title: "two" },
        { const: 3, title: "three" },
        { const: 4, title: "four" },
      ],
    },
    enumAnyOf: {
      type: "array",
      title: "Enum anyOf (non-standard)",
      anyOf: [
        { const: 1, title: "one" },
        { const: 2, title: "two" },
        { const: 3, title: "three" },
        { const: 4, title: "four" },
      ],
    },
    enumTagSelectAnyOf: {
      type: "array",
      title: "Enum searchable tag select anyOf",
      anyOf: [
        { const: 1, title: "one" },
        { const: 2, title: "two" },
        { const: 3, title: "three" },
        { const: 4, title: "four" },
      ],
    },
    enumSearchableOneOf: {
      type: "string",
      title: "Enum searchable select oneOf",
      oneOf: [
        { const: "one", title: "One" },
        { const: "two", title: "Two" },
        { const: "three", title: "Three" },
        { const: "four", title: "Four" },
      ],
    },
  },
};

const uiSchema = {
  enumTagSelectAnyOf: {
    "ui:field": "SearchableMultiTagSelectField",
    "ui:options": {
      placeholder: "Search...",
      emptySearchMessage: "...no matching results found",
    },
  },
  enumSearchableOneOf: {
    "ui:field": "SearchableSelectField",
    "ui:options": {
      placeholder: "Search...",
      clearButtonText: "Clear",
      expandText: "More results",
      emptySearchMessage: "...no matching results found",
    },
  },
};
```

### Alternative fields

There are alternative fields available for certain types.
For `type: "string"` there is `DateField`, `TextAreaField` and partly `MultipleChoiceField`.

`DateField` stores ISO (YYYY-MM-DD), displays in the browser's language by default (or uiSchema["ui:props"].locale if provided), accepts pasted dates in many formats (e.g. 1.2.2025, 01/02/2025, 2025-01-01), includes a native calendar picker, and respects min/max (ISO).

```tsx
const schema = {
  properties: {
    date: { type: "string", title: "Date" },
  },
};

const uiSchema = {
  date: { 
    "ui:field": "DateField",
    "ui:props": {
      locale: "fi" 
     },
  }
};
```

`TextAreaField` gives more space for a longer string input.

```tsx
const schema = {
  properties: {
    story: { type: "string", title: "A short story" },
  },
};

const uiSchema = {
  story: { "ui:field": "TextAreaField" },
};
```

`MultipleChoiceField` takes an array of `oneOf` or `anyOf`.
If you are creating a schema using `oneOf`, the type of the field is the value returned by the chosen enum option (e.g. "string" or "number").
If you are creating a schema using `anyOf`, the type of the field is "array", since the returned value will be in an array format, even if only one option is selected.

The choices provided inside an `oneOf`array will be presented on the UI as Radio button selectables and only one of the given values can be selected.

The choices provided inside an `anyOf` array will be presented on the UI as Checkbox selectables and multiple values can be selected.

`MultipleChoiceField` comes with a small number of predefined styles, which are `inline`, `oneColumn`, `twoColumns` and `threeColumns`. When no style is given, the choice options are listed in a row under the title.

```tsx
const schema = {
  properties: {
    feeling: {
      type: "string",
      title: "How are you feeling today?",
      oneOf: [
        { const: "happy", title: "Happy" },
        { const: "creative", title: "Creative" },
        { const: "tired", title: "Tired" },
        { const: "nope", title: "Please don't even ask..." },
      ],
    },
    food: {
      type: "array",
      title: "What would you like to eat today? (style: oneColumn)",
      anyOf: [
        { const: "breakfast", title: "Breakfast" },
        { const: "secondBreakfast", title: "What about second breakfast?" },
        { const: "elevenses", title: "Elevenses" },
        { const: "luncheon", title: "Luncheon" },
        { const: "afternoonTea", title: "Afternoon tea" },
        { const: "dinner", title: "Dinner" },
        { const: "supper", title: "Supper" },
      ],
    },
  },
};

const uiSchema = {
  feeling: {
    "ui:field": "MultipleChoiceField",
    "ui:options": {
      style: "twoColumns",
    },
  },
  food: {
    "ui:field": "MultipleChoiceField",
    "ui:options": {
      style: "oneColumn",
    },
  },
};
```

For `type: "boolean"` there is `RadioField` and `ToggleField`.
`RadioField` can take labels for the "true" and "false" options. If left empty, the default labels will be "true" and "false".

```tsx
const schema = {
  properties: {
    question: { type: "boolean", title: "Are you ready?" },
    mode: { type: "boolean", title: "Toggle mode" },
  },
};

const uiSchema = {
  question: {
    "ui:field": "RadioField",
    "ui:label": { true: "yes", false: "no" },
  },
  mode: { "ui:field": "ToggleField" },
};
```

The alternative fields are customisable. The customisable elements are:

```tsx
ToggleSwitchField.styles = {
  ToggleLabelStyle: ToggleLabel,
  ToggleFieldInputStyle: ToggleFieldInput,
  ToggleStyle: Toggle,
};

RadioButtonField.styles = {
  RadioButtonFieldContainerStyle: RadioButtonFieldContainer,
  RadioButtonFieldElementStyle: RadioButtonFieldElement,
  RadioButtonFieldInputStyle: RadioButtonFieldInput,
  ButtonLabelStyle: ButtonLabel,
  ButtonLabelTrueStyle: ButtonLabelTrue,
  ButtonLabelFalseStyle: ButtonLabelFalse,
};
```

### Collapsible object field

Fields of `type: "object"` can be made collapsible via the respective uiSchema setting.

```tsx
const collapsibleSchema: Schema = {
  title: "FORM WITH COLLAPSIBLE OBJECT",
  properties: {
    colObject: {
      type: "object",
      title: "Collapsible Object",
      properties: { summer: { type: "string" }, winter: { type: "string" } },
    },
  },
};

const collapsibleUiSchema: UiSchema = {
  colObject: { "ui:options": { isCollapsible: true } },
};
```

### Conditional fields

Conditional fields depending on a parent field input can be given with an `if-then-else` logic inside the schema making use of `allOf`. While `if`and `then` are required to make the condition work, `else` is optional.

```tsx
const conditionsSchema = {
  type: "object",
  properties: {
    animal: {
      type: "string",
      oneOf: [
        { const: "cat", title: "Cat" },
        { const: "fish", title: "Fish" },
        { const: "dog", title: "Dog" },
      ],
    },
  },
  allOf: [
    {
      if: {
        properties: {
          animal: {
            const: "cat",
          },
        },
      },
      then: {
        properties: {
          food: {
            type: "string",
            enum: ["meat", "grass", "fish"],
          },
        },
        required: ["food"],
      },
    },
    {
      if: {
        properties: {
          animal: {
            const: "fish",
          },
        },
      },
      then: {
        properties: {
          food: {
            type: "string",
            enum: ["insect", "worms"],
          },
          water: {
            type: "string",
            enum: ["lake", "sea"],
          },
        },
        required: ["food", "water"],
      },
      else: {
        properties: {
          likesWalkiesWhere: {
            type: "string",
            enum: ["park", "forest", "garden"],
          },
        },
      },
    },

    {
      required: ["animal"],
    },
  ],
};
```

Another option to give conditional fields is using `dependencies`-object inside the main schema.
Dependencies can either be triggered by the conditional-giving property being present in the form values, or by the conditional-giving property having a certain value.

To have value-dependent conditionals, the `dependencies`-object needs to have an `oneOf`-array listing the possible conditional-giving values matching the enum-values of the conditional-giving field. Each case in in the `oneOf` array can either have no conditional fields, then the only key-value pair given inside `properties` is the matching conditional (see "no type"), or an unlimited amount of conditional fields listed after the matching conditional.

```tsx
const dependenciesSchema: Schema = {
  type: "object",
  properties: {
    creditCard: {
      type: "number",
      title: "Credit card number",
      description:
        "Giving any input to this field will trigger its dependent field 'billing_address'",
    },
    fieldType: {
      type: "string",
      description:
        "Selecting an option in this field will render the correct dependent field from the provided oneOf-array",
      enum: ["string", "object", "array"],
    },
  },
  dependencies: {
    creditCard: {
      properties: {
        billing_address: {
          type: "object",
          description: "This object depends on creditCard field input",
          properties: {
            street: { type: "string", title: "Street" },
            city: { type: "string", title: "City" },
            postalCode: { type: "number", title: "Postal code" },
          },
        },
      },
      required: ["billing_address"],
    },
    fieldType: {
      oneOf: [
        {
          properties: {
            fieldType: {
              enum: ["no type"],
            },
          },
        },
        {
          properties: {
            fieldType: {
              enum: ["string"],
            },
            stringField: {
              type: "string",
              title: "StringField",
            },
          },
        },
        {
          properties: {
            fieldType: {
              enum: ["object"],
            },
            objectField: {
              type: "object",
              properties: {
                objectString: {
                  type: "string",
                  title: "StringField in Object",
                },
              },
            },
          },
        },
        {
          properties: {
            fieldType: {
              enum: ["array"],
            },
            arrayField: {
              type: "array",
              items: [{ type: "string", title: "StringField in fixed array" }],
            },
          },
        },
      ],
    },
  },
};
```

Using `if-else-then` or `dependencies` depends mostly on the use case. For recursion, a combination of `$ref` and `dependencies`is recommended.

### Schema reference $ref

Schemas can be referenced. A referenced schema can be given to a property using `$ref` keyword. The definition for the referenced schema needs to be inside `definitions` object inside the main schema. The value-string given to $ref-key must always look like this:
`'#/definitions/{addMatchingKeyHere}'`

In order to give uiSchema to reused or recursive fields defined in `schema.definitons`, the uiSchema needs to be given to the matching prop of `definitions`-object. This way, the given uiProps are added to the referencing field, no matter how deeply nested it is inside the schema.

The schema of the field and the referenced schema are merged, meaning that aspects of the reused schema can be adapted:

```tsx
const schema: Schema = {
  type: "object",
  properties: {
    address: {
      $ref: "#/definitions/address",
      required: ["postcode"],
      default: { country: "US" },
      properties: {
        apartmentNumber: { type: "string", title: "Apartment number" },
      },
    },
  },
  definitions: {
    address: {
      type: "object",
      title: "Address",
      required: ["country", "city", "street"],
      properties: {
        country: { type: "string", title: "Country" },
        city: { type: "string", title: "City" },
        street: { type: "string", title: "Street" },
        postcode: { type: "string", title: "Postcode" },
      },
    },
  },
};
```

The resulting schema of the `address` field:

```tsx
{
  type: "object",
  title: "Address",
  required: ["country", "city", "street", "postcode"],
  default: { country: "US" },
  properties: {
    country: { type: "string", title: "Country" },
    city: { type: "string", title: "City" },
    street: { type: "string", title: "Street" },
    postcode: { type: "string", title: "Postcode" },
    apartmentNumber: { type: "string", title: "Apartment number" },
  },
}
```

If aspects of the `uiSchema` need to be adapted, `uiSchema.definitions` of that field can be overridden inside `uiSchema` by giving the desired properties to the respective field key. In the following schema/uiSchema `petBirthDate` can be used as an example. The settings are overriding the title of the field from the original "When is your birthday?" to "When is the birthday of your pet?".

```tsx
const familyObjectField: Schema = {
  type: "object",
  title: "My family member",
  properties: {
    relationship: { type: "string", title: "Relationship status" },
    names: { $ref: "#/definitions/names" },
  },
};

const refDefSchema: Schema = {
  title: "References and Definitions",
  type: "object",
  properties: {
    names: {
      $ref: "#/definitions/names",
    },
    birthDate: {
      $ref: "#/definitions/birthDate",
    },
    family: {
      type: "object",
      title: "Family",
      properties: {
        mother: {
          type: "array",
          title: "Maternal family",
          additionalItems: familyObjectField,
        },
        father: {
          type: "array",
          title: "Paternal family",
          additionalItems: familyObjectField,
        },
      },
    },
    pet: { type: "string", title: "Pet name" },
    petBirthDate: { $ref: "#/definitions/birthDate" },
  },

  definitions: {
    names: {
      type: "object",
      title: "Name",
      description:
        "The schema for this object comes from the 'definitions' object inside the main schema",
      properties: {
        firstName: { type: "string", title: "First name" },
        middleNames: {
          type: "array",
          title: "Middle names",
          additionalItems: { $ref: "#/definitions/middleName" },
        },
        lastName: { type: "string", title: "Last name" },
      },
    },
    birthDate: { type: "string", title: "When is your birthday?" },
    middleName: { type: "string", title: " Middle name" },
  },
};

const refDefUiSchema: UiSchema = {
  petBirthDate: { "ui:title": "When is the birthday of your pet?" },
  definitions: {
    birthDate: { "ui:field": "DateField" },
    names: {
      history: { "ui:field": "TextAreaField" },
      middleNames: { "ui:info": { content: "You can leave those blank" } },
    },
    middleName: {
      props: {
        title: {
          hidden: true,
        },
      },
    },
  },
};
```

### Ordering fields

The order of fields can be set using `"ui:order": ["fieldName"]` inside the uiSchema.
If using `"ui:order"`, all fields that should be visible in the form need to be included in the given array. Fields excluded will not show on the form, in the following they will be referred to as `"hidden fields"`.

```tsx
const schema = {
  properties: {
    secondField: { type: "string" },
    firstField: { type: "string" },
    thirdField: { type: "string" },
  },
};

const uiSchema = {
  "ui:order": ["firstField", "secondField", "thirdField"],
};
```

Fields excluded will not show on the form. So in the following example "thirdField" will be a `hidden field`.

```tsx
const schema = {
  properties: {
    secondField: { type: "string" },
    firstField: { type: "string" },
    thirdField: { type: "string" },
  },
};

const uiSchema = {
  "ui:order": ["firstField", "secondField"],
};
```

Using `"*"`-wildcard inside the array will add all property fields that were not specifically added to the order-array.

```tsx
const schema = {
  properties: {
    secondField: { type: "string" },
    firstField: { type: "string" },
    thirdField: { type: "string" },
  },
};

const uiSchema = {
  "ui:order": ["secondField", "*"],
};
```

In this example, the resulting order would be ["secondField", "firstField", "thirdField"].

Wildcard `"*"` and `hidden fields` cannot be combined.

Order can be used to group items inside object to sections.

```tsx
const schema = {
  properties: {
    firstField: { type: "string" },
    secondField: { type: "string" },
    thirdField: { type: "string" },
  },
};

const uiSchema = {
  "ui:order": [
    "firstField",
    {title: "More fields", fields: ["secondField", "thirdField"] collapsible: true}
  ],
};
```

In `recursive fields` order can be given to the items themselves as well as to the fields the item is holding.

```tsx
const uiSchema: UiSchema = {
  definitions: {
    Field: {
      "ui:order": ["jelly", "*", "bean"], // gives order to the additional items
      properties: {
        "ui:order": ["ui:title", "additionalProperties", "_fieldType", "type"], // gives order to the fields inside the additional item
      },
      jelly: {
        "ui:help": "I should be first!",
        "ui:order": ["additionalProperties", "_fieldType", "ui:title", "type"], // gives order to the fields inside "jelly" items
      },
      bean: { "ui:help": "I should be last!" },
    },
  },
};
```



## Props

`Form` component props:

```tsx
<Form
  // Required
  schema={schema} // Form schema
  // Optional
  uiSchema={uiSchema} // Form UI schema
  className={"className"} // Form class name
  fields={fields} // Additional custom field components
  templates={templates} // Additional custom field templates
  components={components} // Override form components (e.g. wrappers) with custom ones
  formContext={formContext} // Form context
  useItemsAsAdditionalItems={true} // Allow "items" to be used as "additionalItems" in array fields that are missing "additionalItems", false by default
  hideSubmit={true} // hides submit-button of the form
/>
```

### Special handling of conflicting keys in uiSchema

In case one of your property names matches a reserved key in `uiSchema` (e.g. `ui:description`), you can use the syntax `> propertyName` to target the property.

```tsx
const schema = {
  type: "object",
  properties: {
    "ui:description": { type: "string", title: "Description" },
  },
};

const uiSchema = {
  // This would be interpreted as the description for the parent object
  "ui:description": "Form description",
  
  // This targets the property named "ui:description"
  "> ui:description": {
    "ui:placeholder": "Enter description...",
  },
};
```