This is an automated email from the ASF dual-hosted git repository.

ljmotta pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-tools.git


The following commit(s) were added to refs/heads/main by this push:
     new f331fb04f06 kie-issues#1828: Implement ListField in the 
“form-code-generator-bootstrap4-theme” package (#2941)
f331fb04f06 is described below

commit f331fb04f0641354b1a83d9b0796be02ac3f8d82
Author: Luiz João Motta <[email protected]>
AuthorDate: Fri Feb 28 16:27:42 2025 -0300

    kie-issues#1828: Implement ListField in the 
“form-code-generator-bootstrap4-theme” package (#2941)
---
 .rat-excludes                                      |  12 +
 .../src/api/types.ts                               |   5 +-
 .../resources/staticCode/listHelperFunctions.txt   | 192 +++++++++++++++
 .../templates/checkbox.setModelData.template       |   2 +-
 .../src/resources/templates/checkbox.template      |   4 +-
 .../templates/checkboxGroup.setModelData.template  |   2 +-
 .../src/resources/templates/checkboxGroup.template |  10 +-
 .../resources/templates/date.setModelData.template |   2 +-
 .../src/resources/templates/date.template          |  16 +-
 .../src/resources/templates/form.template          |  24 +-
 .../src/resources/templates/formGroup.template     |   2 +-
 .../templates/input.setModelData.template          |   2 +-
 .../src/resources/templates/input.template         |  14 +-
 .../templates/listField.globalFunctions.template   |  12 +
 .../templates/listField.setModelData.template      |   6 +
 .../src/resources/templates/listField.template     |  15 ++
 .../templates/listField.writeModelData.template    |   5 +
 .../templates/nestField.globalFunctions.template   |   5 +
 .../src/resources/templates/nestField.template     |   2 +-
 .../src/resources/templates/number.template        |  18 +-
 .../templates/radioGroup.setModelData.template     |   2 +-
 .../src/resources/templates/radioGroup.template    |  10 +-
 .../templates/select.setModelData.template         |   8 +-
 .../src/resources/templates/select.template        |  10 +-
 .../src/uniforms/AutoField.tsx                     |   5 +-
 .../src/uniforms/AutoForm.tsx                      |   7 +-
 .../src/uniforms/BoolField.tsx                     |   9 +-
 .../src/uniforms/CheckBoxGroupField.tsx            |  24 +-
 .../src/uniforms/DateField.tsx                     |   9 +-
 .../src/uniforms/{NestField.tsx => ListField.tsx}  |  53 ++--
 .../src/uniforms/NestField.tsx                     |  34 ++-
 .../src/uniforms/NumField.tsx                      |   9 +-
 .../src/uniforms/RadioField.tsx                    |  24 +-
 .../src/uniforms/SelectField.tsx                   |  26 +-
 .../src/uniforms/TextField.tsx                     |   9 +-
 .../src/uniforms/UnsupportedField.tsx              |  12 +-
 .../src/uniforms/index.ts                          |   1 +
 .../src/uniforms/renderForm.tsx                    |   4 +-
 .../{NestedFieldInput.tsx => ListFieldInput.tsx}   |  16 +-
 .../src/uniforms/rendering/NestedFieldInput.tsx    |   7 +-
 .../src/uniforms/rendering/RenderingUtils.tsx      |  29 ++-
 .../templates/AbstractFormGroupTemplate.ts         | 108 ++++++++
 .../src/uniforms/templates/AutoFormTemplate.ts     |   8 +-
 .../src/uniforms/templates/BoolFieldTemplate.ts    |  62 +++--
 .../templates/CheckboxGroupFieldTemplate.ts        |  70 +++---
 .../src/uniforms/templates/DateFieldTemplate.ts    |   4 +-
 .../src/uniforms/templates/ListFieldTemplate.ts    | 155 ++++++++++++
 .../src/uniforms/templates/NestFieldTemplate.ts    |  50 ++--
 .../src/uniforms/templates/NumFieldTemplate.ts     |   4 +-
 .../uniforms/templates/RadioGroupFieldTemplate.ts  |  76 +++---
 .../src/uniforms/templates/SelectFieldTemplate.ts  |  74 +++---
 .../src/uniforms/templates/TextFieldTemplate.ts    |   4 +-
 .../src/uniforms/templates/UnsupportedTemplate.ts  |  13 +-
 .../src/uniforms/templates/templates.ts            |   7 +-
 .../src/uniforms/templates/types.ts                |  97 --------
 .../src/uniforms/templates/utils.ts                |  19 +-
 .../tests/AutoField.test.tsx                       |   9 +-
 .../tests/AutoForm.test.tsx                        |   2 +
 ...nsupportedField.test.tsx => ListField.test.tsx} |  37 ++-
 .../tests/UnsupportedField.test.tsx                |  10 +-
 .../tests/__snapshots__/AutoForm.test.tsx.snap     | 273 ++++++++++++++++++++-
 .../tests/__snapshots__/ListField.test.tsx.snap    |   7 +
 .../tests/__snapshots__/NestField.test.tsx.snap    |   4 +-
 .../__snapshots__/UnsupportedField.test.tsx.snap   |   2 +-
 64 files changed, 1245 insertions(+), 508 deletions(-)

diff --git a/.rat-excludes b/.rat-excludes
index 34610b5e47a..4e47f2f52e3 100644
--- a/.rat-excludes
+++ b/.rat-excludes
@@ -378,6 +378,8 @@ getCheckboxGroupValue.txt
 getMultipleSelectValue.txt
 # 
packages/form-code-generator-bootstrap4-theme/src/resources/staticCode/getRadioGroupValue.txt
 getRadioGroupValue.txt
+# 
packages/form-code-generator-bootstrap4-theme/src/resources/staticCode/listHelperFunctions.txt
+listHelperFunctions.txt
 # 
packages/form-code-generator-bootstrap4-theme/src/resources/staticCode/setCheckboxGroupValue.txt
 setCheckboxGroupValue.txt
 # 
packages/form-code-generator-bootstrap4-theme/src/resources/staticCode/setRadioGroupValue.txt
@@ -412,6 +414,16 @@ input.setModelData.template
 input.template
 # 
packages/form-code-generator-bootstrap4-theme/src/resources/templates/input.writeModelData.template
 input.writeModelData.template
+# 
packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.globalFunctions.template
+listField.globalFunctions.template
+# 
packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.setModelData.template
+listField.setModelData.template
+# 
packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.template
+listField.template
+# 
packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.writeModelData.template
+listField.writeModelData.template
+# 
packages/form-code-generator-bootstrap4-theme/src/resources/templates/nestField.globalFunctions.template
+nestField.globalFunctions.template
 # 
packages/form-code-generator-bootstrap4-theme/src/resources/templates/nestField.setModelData.template
 nestField.setModelData.template
 # 
packages/form-code-generator-bootstrap4-theme/src/resources/templates/nestField.template
diff --git a/packages/form-code-generator-bootstrap4-theme/src/api/types.ts 
b/packages/form-code-generator-bootstrap4-theme/src/api/types.ts
index e541e0109f0..5473446869b 100644
--- a/packages/form-code-generator-bootstrap4-theme/src/api/types.ts
+++ b/packages/form-code-generator-bootstrap4-theme/src/api/types.ts
@@ -31,8 +31,9 @@ export interface FormElement<REFERENCE_TYPE> extends 
CodeGenElement {
 
   disabled?: boolean;
 
-  setValueFromModelCode?: CodeFragment;
-  writeValueToModelCode?: CodeFragment;
+  setValueFromModelCode: CodeFragment | undefined;
+  writeValueToModelCode: CodeFragment | undefined;
+  globalFunctions: CodeFragment | undefined;
 }
 
 export interface FormInput extends FormElement<InputReference> {}
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/resources/staticCode/listHelperFunctions.txt
 
b/packages/form-code-generator-bootstrap4-theme/src/resources/staticCode/listHelperFunctions.txt
new file mode 100644
index 00000000000..bcda00c8bc2
--- /dev/null
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/resources/staticCode/listHelperFunctions.txt
@@ -0,0 +1,192 @@
+// List Field Helper functions -- START --
+function delListItem(name, minCount, itemIndex, onDelListItem) {
+  const formData = getFormData();
+  const value = accessObjectPath(formData, name) ?? [];
+
+  if ((minCount ?? 0) <= value.length) {
+    document.getElementById(`item-${name}.${itemIndex}`).remove();
+  }
+
+  // Re-organize list
+  Array.from(document.getElementById(name).childNodes ?? [])
+    .filter((node) => node.nodeType === 1)
+    .forEach((element, index) => {
+      const delElement = element.querySelector(`[id='remove-item-${name}']`);
+      delElement.onclick = function () {
+        onDelListItem(name, `${index}`);
+      };
+      delElement.onkeydown = function () {
+        onDelListItem(name, `${index}`);
+      };
+
+      const inputOrSelect = element.querySelector("input, select");
+      inputOrSelect.id = `${name}.${index}`;
+      inputOrSelect.name = `${name}.${index}`;
+
+      element.id = `item-${name}.${index}`;
+    });
+}
+
+function addListItem(name, defaultValue, maxCount, childrenHtml, functionName) 
{
+  const formData = getFormData();
+  let value = accessObjectPath(formData, name) ?? [];
+  const itemIndex = value.length;
+  if (maxCount !== undefined && maxCount > value.length) {
+    value = value.concat([defaultValue]);
+  } else {
+    value = value.concat([defaultValue]);
+  }
+  const listContainer = document.getElementById(name);
+  const newItem = document.createElement("div");
+  newItem.class = "row";
+  newItem.id = `item-${name}.${itemIndex}`;
+  newItem.innerHTML = `
+<div class="col-1">
+<span id="remove-item-${name}" name="Remove Item" class="badge badge-pill" 
role="button" tabindex="0" onclick="onDelListItem${functionName}('${name}', 
${itemIndex})" onkeydown="onDelListItem${functionName}('${name}', 
${itemIndex})">
+  <i className="octicon octicon-dash"><span>-</span></i>
+</span>
+</div>
+<div class="col-11">
+${childrenHtml}
+</div>
+`;
+  listContainer.appendChild(newItem);
+  // Replace all "$" from `input` and `select` with the `itemIndex`
+  const newItemElement = document.getElementById(newItem.id);
+  [
+    ...newItemElement.querySelectorAll('input[id*="$"], select[id*="$"]'),
+  ].forEach((el) => {
+    if (el.id.split(".").pop() !== "$") {
+      el.id = `${name}.${itemIndex}.${el.id.split(".").pop()}`;
+      el.name = `${name}.${itemIndex}.${el.id.split(".").pop()}`;
+    } else {
+      el.id = `${name}.${itemIndex}`;
+      el.name = `${name}.${itemIndex}`;
+    }
+  });
+  // Get the root element of a nested list
+  [...newItemElement.querySelectorAll("div[role='list']")].forEach(
+    (nestedListContainer) => {
+      if (!nestedListContainer) {
+        return;
+      }
+      // Replace "$" from the root element if necessary
+      const nestedListPathWithIndex = splitLastOccurrence(
+        nestedListContainer.id,
+        "$"
+      )?.[1]?.replace("$", `${itemIndex}`);
+      nestedListContainer.id = nestedListPathWithIndex
+        ? `${name}.${nestedListPathWithIndex}`
+        : name;
+      // Replace "$" from all nested Add Item element
+      [...newItemElement.querySelectorAll("[id^=add-item]")].forEach(
+        (addItem) => {
+          const addItemPathWithIndex = splitLastOccurrence(
+            addItem.id,
+            "$"
+          )?.[1]?.replace("$", `${itemIndex}`);
+          addItem.id = addItemPathWithIndex
+            ? `add-item-${name}.${addItemPathWithIndex}`
+            : `add-item-${name}`;
+          const addItemOnClick = addItem.getAttribute("onclick");
+          if (addItemOnClick) {
+            addItem.setAttribute(
+              "onclick",
+              getFunctionName(addItemOnClick, itemIndex)
+            );
+          }
+          const addItemOnKeydown = addItem.getAttribute("onkeydown");
+          if (addItemOnKeydown) {
+            addItem.setAttribute(
+              "onkeydown",
+              getFunctionName(addItemOnKeydown, itemIndex)
+            );
+          }
+        }
+      );
+      // Replace "$" from the Remove Item element
+      [...newItemElement.querySelectorAll("[id^=remove-item]")].forEach(
+        (removeItem) => {
+          const removeItemPathWithIndex = splitLastOccurrence(
+            removeItem.id,
+            "$"
+          )?.[1]?.replace("$", `${itemIndex}`);
+          removeItem.id = removeItemPathWithIndex
+            ? `remove-item-${name}.${removeItemPathWithIndex}`
+            : `remove-item-${name}`;
+          const removeItemOnClick = removeItem.getAttribute("onclick");
+          if (removeItemOnClick) {
+            removeItem.setAttribute(
+              "onclick",
+              getFunctionName(removeItemOnClick, itemIndex)
+            );
+          }
+          const removeItemOnKeydown = removeItem.getAttribute("onkeydown");
+          if (removeItemOnKeydown) {
+            removeItem.setAttribute(
+              "onkeydown",
+              getFunctionName(removeItemOnKeydown, itemIndex)
+            );
+          }
+        }
+      );
+    }
+  );
+}
+function getFunctionName(functionCall, itemIndex) {
+  const [call, argument, end] = functionCall.split("'");
+  const [argumentName, argumentNameToReplace] = splitLastOccurrence(
+    argument,
+    "$"
+  );
+  return `${call}'${argumentName}${
+    argumentNameToReplace?.replace("$", `${itemIndex}`) ?? ""
+  }'${end}`;
+}
+function splitLastOccurrence(str, char) {
+  const lastIndex = str.lastIndexOf(char);
+  if (lastIndex === -1) {
+    return [str];
+  }
+  return [str.substring(0, lastIndex), str.substring(lastIndex)];
+}
+function getListValue(itemListElement) {
+  function setValue(obj, path, value) {
+    const keys = path.split(".");
+    let current = obj;
+
+    for (let i = 0; i < keys.length; i++) {
+      const key = keys[i];
+      const isArray = !isNaN(keys[i + 1]);
+
+      if (i === keys.length - 1) {
+        current[key] = value;
+      } else {
+        if (!current[key]) {
+          current[key] = isArray ? [] : {};
+        }
+        if (isArray && !Array.isArray(current[key])) {
+          current[key] = [];
+        }
+        current = current[key];
+      }
+    }
+  }
+
+  function traverse(element, result, path = "") {
+    element.querySelectorAll("input, select").forEach((input) => {
+      if (input.id) {
+        setValue(result, input.id, input.value || "");
+      }
+    });
+  }
+
+  const result = {};
+  traverse(itemListElement, result);
+  const [_, path] = itemListElement.id.split("item-");
+  return accessObjectPath(result, path);
+}
+function accessObjectPath(obj, objPath) {
+  return objPath.split(".")?.reduce((acc, pathPiece) => acc?.[pathPiece], obj);
+}
+// List Field Helper functions -- END --
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkbox.setModelData.template
 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkbox.setModelData.template
index 5c03f19358e..7eba58bbad8 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkbox.setModelData.template
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkbox.setModelData.template
@@ -1 +1 @@
-document.getElementById("<%=id%>").checked = data?.<%=path%>;
\ No newline at end of file
+<% if (isListItem) { %>document.getElementById(`<%=id%>`).checked = value<%= 
valuePath %> ?? "";<% } else { %>document.getElementById("<%=id%>").checked = 
data?.<%=path%>;<% } %>
\ No newline at end of file
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkbox.template
 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkbox.template
index cfd3b1670d7..561bed85ad9 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkbox.template
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkbox.template
@@ -1,4 +1,4 @@
 <div class="form-check">
-    <input type="checkbox" id="<%=props.id%>" name="<%=props.name%>" 
class="form-check-input"<%=props.disabled ? " disabled" : ""%><%=props.checked 
? " checked" : ""%>/>
-    <label class="form-check-label" 
for="<%=props.id%>"><%=props.label%></label>
+    <input type="checkbox" id="<%=id%>" name="<%=name%>" 
class="form-check-input"<%=disabled ? " disabled" : ""%><%=checked ? " checked" 
: ""%>/>
+    <label class="form-check-label" for="<%=id%>"><%=label%></label>
 </div>
\ No newline at end of file
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkboxGroup.setModelData.template
 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkboxGroup.setModelData.template
index 8da59d28557..11f164b0205 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkboxGroup.setModelData.template
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkboxGroup.setModelData.template
@@ -1 +1 @@
-setCheckboxGroupValues("<%=name%>", data?.<%=path%>);
\ No newline at end of file
+<% if (isListItem) { %>setCheckboxGroupValues(`<%=name%>`, value<%= valuePath 
%> ?? "");<% } else { %>setCheckboxGroupValues("<%=name%>", data?.<%=path%>);<% 
} %>
\ No newline at end of file
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkboxGroup.template
 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkboxGroup.template
index 92a7aaf19d9..a0993c88ed0 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkboxGroup.template
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkboxGroup.template
@@ -1,11 +1,11 @@
-<% _.each(props.options, function(option) { %>
+<% _.each(options, function(option) { %>
 <div class="form-check">
     <input type="checkbox" class="form-check-input"
-        id="<%=props.id + '__' + option.value%>"
-        name="<%=props.name%>"
+        id="<%=id + '__' + option.value%>"
+        name="<%=name%>"
         value="<%=option.value%>"
-        <%=props.disabled ? "disabled" : ""%>
+        <%=disabled ? "disabled" : ""%>
         <%=option.checked ? "checked" : ""%>/>
-    <label class="form-check-label" for="<%=props.id + '__' + 
option.value%>"><%=option.label%></label>
+    <label class="form-check-label" for="<%=id + '__' + 
option.value%>"><%=option.label%></label>
 </div>
 <% }); %>
\ No newline at end of file
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/date.setModelData.template
 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/date.setModelData.template
index 153c02c53ac..725de6e72a7 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/date.setModelData.template
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/date.setModelData.template
@@ -1 +1 @@
-document.getElementById("<%=id%>").value = data?.<%=path%> ? new 
Date(data?.<%=path%>).toISOString().slice(0, 16) : "";
\ No newline at end of file
+<% if (isListItem) { %>document.getElementById(`<%=id%>`).value = value<%= 
valuePath %> ? new Date(value<%= valuePath %>).toISOString().slice(0, 16) : 
"";<% } else { %>document.getElementById("<%=id%>").value = data?.<%=path%> ? 
new Date(data?.<%=path%>).toISOString().slice(0, 16) : "";<% } %>
\ No newline at end of file
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/date.template
 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/date.template
index 066f4956797..39cbe1c3794 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/date.template
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/date.template
@@ -1,11 +1,11 @@
 <input
       type="datetime-local"
-      id="<%=props.id%>"
-      name="<%=props.name%>"
+      id="<%=id%>"
+      name="<%=name%>"
       class="form-control"
-      <%=props.autoComplete==='off' ? ' autoComplete="off"' : ''%>
-      <%=props.disabled ? "disabled" : ""%>
-      <%=props.placeholder ? `placeholder="${props.placeholder}"` : ''%>
-      <%=props.min ? `min="${props.min}"` : ''%>
-      <%=props.max ? `max="${props.max}"` : ''%>
-      <%=props.value ? `value="${props.value}"` : ''%>/>
\ No newline at end of file
+      <%=autoComplete==='off' ? ' autoComplete="off"' : ''%>
+      <%=disabled ? "disabled" : ""%>
+      <%=placeholder ? `placeholder="${placeholder}"` : ''%>
+      <%=min ? `min="${min}"` : ''%>
+      <%=max ? `max="${max}"` : ''%>
+      <%=value ? `value="${value}"` : ''%>/>
\ No newline at end of file
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/form.template
 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/form.template
index 42d1b4af13f..71327a87fd2 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/form.template
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/form.template
@@ -2,8 +2,30 @@
 <% _.each(props.children, function(child) { %>
     <%=child.html%>
 <% }); %>
-
+<% if (props.submit) { %>
+<input
+    class="btn btn-primary"
+    disabled={<%= props.disabled %>}
+    type="submit"
+/>
+<% } %>
     <script>
+<% const addedGlobalFunctionsCode = []; %>
+<%  _.each(props.children, function(child) { %>
+    <% if (child.globalFunctions && child.globalFunctions.requiredCode) { %>
+        <% _.each(child.globalFunctions.requiredCode, function(codeFragment) { 
%>
+            <% if (!addedGlobalFunctionsCode.includes(codeFragment)) { %>
+                <% addedGlobalFunctionsCode.push(codeFragment); %>
+                <%= codeFragment %>
+        <% } %>
+    <% }); %>
+    <% } %>
+<% }); %>
+<% _.each(props.children, function(child) { %>
+    <% if (child.globalFunctions) { %>
+            <%= child.globalFunctions.code %>
+    <% } %>
+<% }); %>
         /* Utility function that fills the form with the data received from 
the kogito runtime */
         function setFormData(data) {
             if(!data) {
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/formGroup.template
 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/formGroup.template
index 84a5b2a6a79..310e52c0168 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/formGroup.template
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/formGroup.template
@@ -1,4 +1,4 @@
 <div class="form-group">
-    <label for="<%=props.id%>"><%=props.label%></label>
+    <label for="<%= isListItem ? "item-"+id : id %>"><%= label %></label>
     <%= input %>
 </div>
\ No newline at end of file
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/input.setModelData.template
 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/input.setModelData.template
index a116b601037..a9a3bbafd63 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/input.setModelData.template
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/input.setModelData.template
@@ -1 +1 @@
-document.getElementById("<%=id%>").value = data?.<%=path%> ?? "";
\ No newline at end of file
+<% if (isListItem) { %>document.getElementById(`<%=id%>`).value = value<%= 
valuePath %> ?? "";<% } else { %>document.getElementById("<%=id%>").value = 
data?.<%=path%> ?? "";<% } %>
\ No newline at end of file
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/input.template
 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/input.template
index e235979225b..c4699d9a27f 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/input.template
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/input.template
@@ -1,8 +1,8 @@
-<input type="<%=props.type%>"
-    id="<%=props.id%>"
-    name="<%=props.name%>"
+<input type="<%=type%>"
+    id="<%=id%>"
+    name="<%=name%>"
     class="form-control"
-    <%=props.autoComplete==='off' ? ' autoComplete="off"' : ''%>
-    <%=props.disabled ? "disabled" : ""%>
-    <%=props.placeholder ? `placeholder="${props.placeholder}"` : ''%>
-    value="<%=props.value%>"/>
\ No newline at end of file
+    <%=autoComplete==='off' ? ' autoComplete="off"' : ''%>
+    <%=disabled ? "disabled" : ""%>
+    <%=placeholder ? `placeholder="${placeholder}"` : ''%>
+    value="<%=value%>"/>
\ No newline at end of file
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.globalFunctions.template
 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.globalFunctions.template
new file mode 100644
index 00000000000..376698f3355
--- /dev/null
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.globalFunctions.template
@@ -0,0 +1,12 @@
+function onDelListItem<%= functionName %>(name, itemIndex) {
+  const disabled = <%= disabled %>
+  if (!disabled) {
+    delListItem(name, <%= minCount !== undefined ? minCount : "undefined" %>, 
itemIndex, onDelListItem<%= functionName %>)
+  }
+}
+function onAddListItem<%= functionName %>(name, initialSetValues) {
+  const disabled = <%= disabled %>
+  if (!disabled || initialSetValues) {
+    addListItem(name, <%= defaultValue %>, <%= maxCount !== undefined ? 
maxCount : "undefined" %>, `<%= childrenHtml %>`, "<%= functionName %>")
+  }
+}
\ No newline at end of file
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.setModelData.template
 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.setModelData.template
new file mode 100644
index 00000000000..6cb5ed587ef
--- /dev/null
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.setModelData.template
@@ -0,0 +1,6 @@
+<%=path%>?.forEach((value, <%= indexVariableName %>) => {
+// Add element;
+onAddListItem<%= functionName %>(`<%= name %>`, true)
+const <%= prefix %>__currentItem = `<%= name %>.${<%= indexVariableName %>}`
+<%= itemsSetValueFromModel.code %>
+});
\ No newline at end of file
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.template
 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.template
new file mode 100644
index 00000000000..87c354cba3f
--- /dev/null
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.template
@@ -0,0 +1,15 @@
+<div class="form-group">
+    <div class="card mb-3">
+    <div class="card-body">
+        <div class="card-title">
+            <label class="col-form-label"><%=label%></label>
+            <div id="add-item-<%= id %>" name="Add Item" class="badge 
badge-pill float-right" role="button" tabindex="0" <%=disabled ? " disabled" : 
""%>
+                onclick="onAddListItem<%= functionName %>('<%= id %>')"
+                onkeydown="onAddListItem<%= functionName %>('<%= id %>')">
+                <i className="octicon octicon-plus"><span>+</span></i>
+            </div>
+        </div>
+        <div role="list" id="<%= id %>"></div>
+    </div>
+    </div>
+</div>
\ No newline at end of file
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.writeModelData.template
 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.writeModelData.template
new file mode 100644
index 00000000000..4d76efb14c1
--- /dev/null
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.writeModelData.template
@@ -0,0 +1,5 @@
+<% if(!disabled) { %>
+formData.<%=name%> = 
Array.from(document.getElementById("<%=name%>").childNodes).reduce((values, 
element) => {
+  return [...values, getListValue(element)];
+}, []);
+<% } %>
\ No newline at end of file
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/nestField.globalFunctions.template
 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/nestField.globalFunctions.template
new file mode 100644
index 00000000000..bb6e0bed409
--- /dev/null
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/nestField.globalFunctions.template
@@ -0,0 +1,5 @@
+<% _.each(props.children, function(child) { %>
+    <% if (child.globalFunctions) { %>
+        <%= child.globalFunctions.code %>
+    <% } %>
+<% }); %>
\ No newline at end of file
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/nestField.template
 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/nestField.template
index 37aad5ee969..b2387f7f4d2 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/nestField.template
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/nestField.template
@@ -1,6 +1,6 @@
 <fieldset<%=props.disabled ? " disabled" : ""%>>
     <legend><%=props.label%></legend>
-    <div>
+    <div role="group">
 <% _.each(props.children, function(child) { %>
         <%=child.html%>
 <% }); %>
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/number.template
 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/number.template
index 291a8d24f49..ff55355d8a3 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/number.template
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/number.template
@@ -1,12 +1,12 @@
 <input
     type="number"
     class="form-control"
-    id="<%=props.id%>"
-    name="<%=props.name%>"
-    <%=props.autoComplete==='off' ? ' autoComplete="off"' : ''%>
-    <%=props.disabled ? "disabled" : ""%>
-    <%=props.placeholder ? `placeholder="${props.placeholder}"` : ''%>
-    <%=props.min ? `min="${props.min}"` : ''%>
-    <%=props.max ? `max="${props.max}"` : ''%>
-    <%=props.step ? `step="${props.step}"` : ''%>
-    value="<%=props.value%>"/>
\ No newline at end of file
+    id="<%=id%>"
+    name="<%=name%>"
+    <%=autoComplete==='off' ? ' autoComplete="off"' : ''%>
+    <%=disabled ? "disabled" : ""%>
+    <%=placeholder ? `placeholder="${placeholder}"` : ''%>
+    <%=min ? `min="${min}"` : ''%>
+    <%=max ? `max="${max}"` : ''%>
+    <%=step ? `step="${step}"` : ''%>
+    value="<%=value%>"/>
\ No newline at end of file
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/radioGroup.setModelData.template
 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/radioGroup.setModelData.template
index 37c30f4adfa..8218fe9ef05 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/radioGroup.setModelData.template
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/radioGroup.setModelData.template
@@ -1 +1 @@
-setRadioButtonGroupValue("<%=name%>", data?.<%=path%>);
\ No newline at end of file
+<% if (isListItem) { %>setRadioButtonGroupValue(`<%=name%>`, value<%= 
valuePath %> ?? "");<% } else { %>setRadioButtonGroupValue("<%=name%>", 
data?.<%=path%>);<% } %>
\ No newline at end of file
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/radioGroup.template
 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/radioGroup.template
index 916abd3518a..1b7978fa951 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/radioGroup.template
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/radioGroup.template
@@ -1,11 +1,11 @@
-<% _.each(props.options, function(option) { %>
+<% _.each(options, function(option) { %>
 <div class="form-check">
     <input type="radio" class="form-check-input"
-        id="<%=props.id + '__' + option.value%>"
-        name="<%=props.name%>"
+        id="<%=id + '__' + option.value%>"
+        name="<%=name%>"
         value="<%=option.value%>"
-        <%=props.disabled ? "disabled" : ""%>
+        <%=disabled ? "disabled" : ""%>
         <%=option.checked ? "checked" : ""%>/>
-    <label class="form-check-label" for="<%=props.id + '__' + 
option.value%>"><%=option.label%></label>
+    <label class="form-check-label" for="<%=id + '__' + 
option.value%>"><%=option.label%></label>
 </div>
 <% }); %>
\ No newline at end of file
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/select.setModelData.template
 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/select.setModelData.template
index e7639e58e2b..37f33ef13a9 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/select.setModelData.template
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/select.setModelData.template
@@ -1,5 +1,9 @@
-<% if(multiple) { %>
+<% if (isListItem) { %><% if(multiple) { %>
+setMultipleSelectValues(`<%=id%>`, value<%= valuePath %>);
+<% } else { %>
+setSelectValue(`<%=id%>`, value<%= valuePath %>);
+<% } %><% } else { %><% if(multiple) { %>
 setMultipleSelectValues("<%=id%>", data?.<%=path%>);
 <% } else { %>
 setSelectValue("<%=id%>", data?.<%=path%>);
-<% } %>
\ No newline at end of file
+<% } %><% } %>
\ No newline at end of file
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/select.template
 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/select.template
index 01539cebb1c..a1ec70dcdd6 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/select.template
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/select.template
@@ -1,8 +1,8 @@
-<select class="form-control" id="<%=props.id%>" 
name="<%=props.name%>"<%=props.disabled ? " disabled" : ""%><%=props.multiple ? 
" multiple" : ""%>>
-<% if(props.placeHolder) { %>
-    <option value=""><%=props.placeHolder%></option>
+<select class="form-control" id="<%=id%>" name="<%=name%>"<%=disabled ? " 
disabled" : ""%><%=multiple ? " multiple" : ""%>>
+<% if(placeHolder) { %>
+    <option value=""><%=placeHolder%></option>
 <% } %>
-<% _.each(props.options, function(option) { %>
-    <option value="<%=option.value%>"<%=props.value===option.value ? " 
selected" : ""%>><%=option.label%></option>
+<% _.each(options, function(option) { %>
+    <option value="<%=option.value%>"<%=value===option.value ? " selected" : 
""%>><%=option.label%></option>
 <% }); %>
 </select>
\ No newline at end of file
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/AutoField.tsx 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/AutoField.tsx
index 1ecad9d1d07..3ea1aaa96dc 100644
--- a/packages/form-code-generator-bootstrap4-theme/src/uniforms/AutoField.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/src/uniforms/AutoField.tsx
@@ -22,6 +22,7 @@ import {
   BoolField,
   CheckBoxGroupField,
   DateField,
+  ListField,
   NestField,
   NumField,
   RadioField,
@@ -41,10 +42,8 @@ const AutoField = createAutoField((props) => {
   }
 
   switch (props.fieldType) {
-    /*
-    TODO: implement array support
     case Array:
-      return  ListField;*/
+      return ListField;
     case Boolean:
       return BoolField;
     case Date:
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/AutoForm.tsx 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/AutoForm.tsx
index cd9463ba497..174ec799770 100644
--- a/packages/form-code-generator-bootstrap4-theme/src/uniforms/AutoForm.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/src/uniforms/AutoForm.tsx
@@ -32,12 +32,7 @@ export type AutoFormProps = {
 };
 
 const AutoForm: React.FC<AutoFormProps> = (props) => {
-  const properties = {
-    children: renderFormInputs(props.schema),
-  };
-
-  const form: CodeGenElement = renderCodeGenElement(FORM, properties);
-
+  const form: CodeGenElement = renderCodeGenElement(FORM, { children: 
renderFormInputs(props.schema) });
   return <>{escape(form.html)}</>;
 };
 
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/BoolField.tsx 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/BoolField.tsx
index 9254b235195..78f97e40303 100644
--- a/packages/form-code-generator-bootstrap4-theme/src/uniforms/BoolField.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/src/uniforms/BoolField.tsx
@@ -23,6 +23,7 @@ import { FormInput } from "../api";
 
 import { useAddFormElementToBootstrapContext } from 
"./BootstrapCodeGenContext";
 import { CHECKBOX, renderCodeGenElement } from "./templates/templates";
+import { ListItemProps } from "./rendering/ListFieldInput";
 
 export type BoolFieldProps = HTMLFieldProps<
   boolean,
@@ -30,19 +31,19 @@ export type BoolFieldProps = HTMLFieldProps<
   {
     name: string;
     label: string;
+    itemProps?: ListItemProps;
   }
 >;
 
 const Bool: React.FC<BoolFieldProps> = (props: BoolFieldProps) => {
-  const properties = {
+  const element: FormInput = renderCodeGenElement(CHECKBOX, {
     id: props.name,
     name: props.name,
     label: props.label,
     disabled: props.disabled ?? false,
     checked: props.value ?? false,
-  };
-
-  const element: FormInput = renderCodeGenElement(CHECKBOX, properties);
+    itemProps: props.itemProps,
+  });
 
   useAddFormElementToBootstrapContext(element);
 
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/CheckBoxGroupField.tsx
 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/CheckBoxGroupField.tsx
index 27e7eb31922..4330256759f 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/CheckBoxGroupField.tsx
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/CheckBoxGroupField.tsx
@@ -22,6 +22,7 @@ import { connectField, HTMLFieldProps } from "uniforms/cjs";
 import { CHECKBOXGROUP, renderCodeGenElement } from "./templates/templates";
 import { useAddFormElementToBootstrapContext } from 
"./BootstrapCodeGenContext";
 import { FormInput } from "../api";
+import { ListItemProps } from "./rendering/ListFieldInput";
 
 export type CheckBoxGroupProps = HTMLFieldProps<
   string[],
@@ -32,28 +33,25 @@ export type CheckBoxGroupProps = HTMLFieldProps<
     allowedValues?: string[];
     required: boolean;
     transform?(value: string): string;
+    itemProps?: ListItemProps;
   }
 >;
 
 const CheckBoxGroup: React.FC<CheckBoxGroupProps> = (props: 
CheckBoxGroupProps) => {
-  const options =
-    props.allowedValues?.map((option) => {
+  const element: FormInput = renderCodeGenElement(CHECKBOXGROUP, {
+    id: props.name,
+    name: props.name,
+    label: props.label,
+    disabled: props.disabled,
+    itemProps: props.itemProps,
+    options: (props.allowedValues ?? [])?.map((option) => {
       return {
         value: option,
         label: props.transform ? props.transform(option) : option,
         checked: props.value?.includes(option),
       };
-    }) || [];
-
-  const inputProps = {
-    id: props.name,
-    name: props.name,
-    label: props.label,
-    disabled: props.disabled,
-    options: options,
-  };
-
-  const element: FormInput = renderCodeGenElement(CHECKBOXGROUP, inputProps);
+    }),
+  });
   useAddFormElementToBootstrapContext(element);
   return <>{JSON.stringify(element)}</>;
 };
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/DateField.tsx 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/DateField.tsx
index cd98b1e14dc..b8b3c781ebf 100644
--- a/packages/form-code-generator-bootstrap4-theme/src/uniforms/DateField.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/src/uniforms/DateField.tsx
@@ -22,6 +22,7 @@ import { connectField, HTMLFieldProps } from "uniforms/cjs";
 import { DATE, renderCodeGenElement } from "./templates/templates";
 import { useAddFormElementToBootstrapContext } from 
"./BootstrapCodeGenContext";
 import { FormInput } from "../api";
+import { ListItemProps } from "./rendering/ListFieldInput";
 
 export type DateFieldProps = HTMLFieldProps<
   Date,
@@ -32,6 +33,7 @@ export type DateFieldProps = HTMLFieldProps<
     required: boolean;
     max?: Date;
     min?: Date;
+    itemProps?: ListItemProps;
   }
 >;
 
@@ -40,7 +42,7 @@ const Date: React.FC<DateFieldProps> = (props: 
DateFieldProps) => {
     return date?.toISOString().slice(0, -8);
   }
 
-  const properties = {
+  const element: FormInput = renderCodeGenElement(DATE, {
     id: props.name,
     name: props.name,
     label: props.label,
@@ -50,9 +52,8 @@ const Date: React.FC<DateFieldProps> = (props: 
DateFieldProps) => {
     value: formatDate(props.value),
     max: formatDate(props.max),
     min: formatDate(props.min),
-  };
-
-  const element: FormInput = renderCodeGenElement(DATE, properties);
+    itemProps: props.itemProps,
+  });
   useAddFormElementToBootstrapContext(element);
   return <>{JSON.stringify(element)}</>;
 };
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/NestField.tsx 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/ListField.tsx
similarity index 59%
copy from 
packages/form-code-generator-bootstrap4-theme/src/uniforms/NestField.tsx
copy to packages/form-code-generator-bootstrap4-theme/src/uniforms/ListField.tsx
index 2e99c297494..0116b91d206 100644
--- a/packages/form-code-generator-bootstrap4-theme/src/uniforms/NestField.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/src/uniforms/ListField.tsx
@@ -19,14 +19,24 @@
 
 import React, { useContext } from "react";
 import { connectField, context, HTMLFieldProps } from "uniforms/cjs";
-import { renderNestedInputFragmentWithContext } from 
"./rendering/RenderingUtils";
-import { FormElement, FormInput, FormInputContainer } from "../api";
+import { FormInputContainer } from "../api";
+import { renderListItemFragmentWithContext } from "./rendering/RenderingUtils";
 import { useBootstrapCodegenContext } from "./BootstrapCodeGenContext";
-import { NESTED, renderCodeGenElement } from "./templates/templates";
+import { LIST, renderCodeGenElement } from "./templates/templates";
+import { ListItemProps } from "./rendering/ListFieldInput";
+import { getNextIndexVariableName } from "./templates/ListFieldTemplate";
 
-export type NestFieldProps = HTMLFieldProps<object, HTMLDivElement, { 
itemProps?: object }>;
+export type ListFieldProps = HTMLFieldProps<
+  unknown[],
+  HTMLDivElement,
+  {
+    itemProps: ListItemProps;
+    maxCount?: number;
+    minCount?: number;
+  }
+>;
 
-const Nest: React.FunctionComponent<NestFieldProps> = ({
+const List: React.FunctionComponent<ListFieldProps> = ({
   id,
   children,
   error,
@@ -38,33 +48,30 @@ const Nest: React.FunctionComponent<NestFieldProps> = ({
   showInlineError,
   disabled,
   ...props
-}: NestFieldProps) => {
+}: ListFieldProps) => {
   const uniformsContext = useContext(context);
   const codegenCtx = useBootstrapCodegenContext();
 
-  const nestedFields: FormElement<any>[] = [];
-  if (fields) {
-    fields.forEach((field) => {
-      const nestedElement = 
renderNestedInputFragmentWithContext(uniformsContext, field, itemProps, 
disabled);
-      if (nestedElement) {
-        nestedFields.push(nestedElement);
-      } else {
-        console.log(`Cannot render form field for: '${field}'`);
-      }
-    });
-  }
-
-  const properties = {
+  const element: FormInputContainer = renderCodeGenElement(LIST, {
     id: name,
     name: name,
     label: label,
     disabled: disabled,
-    children: nestedFields,
-  };
+    itemProps: itemProps,
+    children: renderListItemFragmentWithContext(
+      uniformsContext,
+      "$",
+      {
+        isListItem: true,
+        indexVariableName: getNextIndexVariableName(itemProps),
+        listName: name,
+      },
+      disabled
+    ),
+  });
 
-  const element: FormInputContainer = renderCodeGenElement(NESTED, properties);
   codegenCtx?.rendered.push(element);
   return <>{JSON.stringify(element)}</>;
 };
 
-export default connectField(Nest);
+export default connectField(List);
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/NestField.tsx 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/NestField.tsx
index 2e99c297494..72f2243bc11 100644
--- a/packages/form-code-generator-bootstrap4-theme/src/uniforms/NestField.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/src/uniforms/NestField.tsx
@@ -23,8 +23,9 @@ import { renderNestedInputFragmentWithContext } from 
"./rendering/RenderingUtils
 import { FormElement, FormInput, FormInputContainer } from "../api";
 import { useBootstrapCodegenContext } from "./BootstrapCodeGenContext";
 import { NESTED, renderCodeGenElement } from "./templates/templates";
+import { ListItemProps } from "./rendering/ListFieldInput";
 
-export type NestFieldProps = HTMLFieldProps<object, HTMLDivElement, { 
itemProps?: object }>;
+export type NestFieldProps = HTMLFieldProps<object, HTMLDivElement, { 
itemProps: ListItemProps }>;
 
 const Nest: React.FunctionComponent<NestFieldProps> = ({
   id,
@@ -42,27 +43,24 @@ const Nest: React.FunctionComponent<NestFieldProps> = ({
   const uniformsContext = useContext(context);
   const codegenCtx = useBootstrapCodegenContext();
 
-  const nestedFields: FormElement<any>[] = [];
-  if (fields) {
-    fields.forEach((field) => {
-      const nestedElement = 
renderNestedInputFragmentWithContext(uniformsContext, field, itemProps, 
disabled);
-      if (nestedElement) {
-        nestedFields.push(nestedElement);
-      } else {
-        console.log(`Cannot render form field for: '${field}'`);
-      }
-    });
-  }
-
-  const properties = {
+  const element: FormInputContainer = renderCodeGenElement(NESTED, {
     id: name,
     name: name,
     label: label,
     disabled: disabled,
-    children: nestedFields,
-  };
-
-  const element: FormInputContainer = renderCodeGenElement(NESTED, properties);
+    itemProps: itemProps,
+    children: fields
+      ? fields.reduce((nestedFields: FormElement<any>[], field) => {
+          const nestedElement = 
renderNestedInputFragmentWithContext(uniformsContext, field, itemProps, 
disabled);
+          if (nestedElement) {
+            nestedFields.push(nestedElement);
+          } else {
+            console.log(`Cannot render form field for: '${field}'`);
+          }
+          return nestedFields;
+        }, [])
+      : [],
+  });
   codegenCtx?.rendered.push(element);
   return <>{JSON.stringify(element)}</>;
 };
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/NumField.tsx 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/NumField.tsx
index 1bb0e8fdaf6..74977cdd707 100644
--- a/packages/form-code-generator-bootstrap4-theme/src/uniforms/NumField.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/src/uniforms/NumField.tsx
@@ -22,6 +22,7 @@ import { connectField, HTMLFieldProps } from "uniforms/cjs";
 import { NUMBER, renderCodeGenElement } from "./templates/templates";
 import { useAddFormElementToBootstrapContext } from 
"./BootstrapCodeGenContext";
 import { FormInput } from "../api";
+import { ListItemProps } from "./rendering/ListFieldInput";
 
 export type NumFieldProps = HTMLFieldProps<
   string,
@@ -32,10 +33,11 @@ export type NumFieldProps = HTMLFieldProps<
     decimal?: boolean;
     min?: string;
     max?: string;
+    itemProps?: ListItemProps;
   }
 >;
 const Num: React.FC<NumFieldProps> = (props: NumFieldProps) => {
-  const properties = {
+  const element: FormInput = renderCodeGenElement(NUMBER, {
     id: props.name,
     name: props.name,
     label: props.label,
@@ -47,9 +49,8 @@ const Num: React.FC<NumFieldProps> = (props: NumFieldProps) 
=> {
     max: props.max,
     min: props.min,
     step: props.decimal ? 0.01 : 1,
-  };
-
-  const element: FormInput = renderCodeGenElement(NUMBER, properties);
+    itemProps: props.itemProps,
+  });
   useAddFormElementToBootstrapContext(element);
   return <>{JSON.stringify(element)}</>;
 };
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/RadioField.tsx 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/RadioField.tsx
index d49e534d110..90ee646a99a 100644
--- a/packages/form-code-generator-bootstrap4-theme/src/uniforms/RadioField.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/src/uniforms/RadioField.tsx
@@ -22,6 +22,7 @@ import { connectField, HTMLFieldProps } from "uniforms/cjs";
 import { RADIOGROUP, renderCodeGenElement } from "./templates/templates";
 import { useAddFormElementToBootstrapContext } from 
"./BootstrapCodeGenContext";
 import { FormInput } from "../api";
+import { ListItemProps } from "./rendering/ListFieldInput";
 
 export type RadioFieldProps = HTMLFieldProps<
   string,
@@ -33,28 +34,25 @@ export type RadioFieldProps = HTMLFieldProps<
     allowedValues?: string[];
     required: boolean;
     disabled: boolean;
+    itemProps?: ListItemProps;
   }
 >;
 
 const Radio = (props: RadioFieldProps) => {
-  const options =
-    props.allowedValues?.map((option) => {
+  const element: FormInput = renderCodeGenElement(RADIOGROUP, {
+    id: props.name,
+    name: props.name,
+    label: props.label,
+    disabled: props.disabled,
+    itemProps: props.itemProps,
+    options: (props.allowedValues ?? [])?.map((option) => {
       return {
         value: option,
         label: props.transform ? props.transform(option) : option,
         checked: props.value === option,
       };
-    }) || [];
-
-  const inputProps = {
-    id: props.name,
-    name: props.name,
-    label: props.label,
-    disabled: props.disabled,
-    options: options,
-  };
-
-  const element: FormInput = renderCodeGenElement(RADIOGROUP, inputProps);
+    }),
+  });
   useAddFormElementToBootstrapContext(element);
   return <>{JSON.stringify(element)}</>;
 };
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/SelectField.tsx 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/SelectField.tsx
index f2dd2ec6a6b..9438ebd1eec 100644
--- a/packages/form-code-generator-bootstrap4-theme/src/uniforms/SelectField.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/src/uniforms/SelectField.tsx
@@ -22,6 +22,7 @@ import { connectField, HTMLFieldProps } from "uniforms/cjs";
 import { renderCodeGenElement, SELECT } from "./templates/templates";
 import { useAddFormElementToBootstrapContext } from 
"./BootstrapCodeGenContext";
 import { FormInput } from "../api";
+import { ListItemProps } from "./rendering/ListFieldInput";
 
 export type SelectInputProps = HTMLFieldProps<
   string[],
@@ -33,31 +34,28 @@ export type SelectInputProps = HTMLFieldProps<
     allowedValues?: string[];
     required: boolean;
     transform?(value: string): string;
+    itemProps?: ListItemProps;
   }
 >;
 
 const Select: React.FC<SelectInputProps> = (props: SelectInputProps) => {
-  const options =
-    props.allowedValues?.map((option) => {
-      return {
-        value: option,
-        label: props.transform ? props.transform(option) : option,
-        checked: props.value?.includes(option),
-      };
-    }) || [];
-
-  const inputProps = {
+  const element: FormInput = renderCodeGenElement(SELECT, {
     id: props.name,
     name: props.name,
     label: props.label,
     multiple: props.fieldType === Array,
     placeHolder: props.placeHolder,
     disabled: props.disabled,
-    options: options,
     value: props.value,
-  };
-
-  const element: FormInput = renderCodeGenElement(SELECT, inputProps);
+    itemProps: props.itemProps,
+    options: (props.allowedValues ?? [])?.map((option) => {
+      return {
+        value: option,
+        label: props.transform ? props.transform(option) : option,
+        checked: props.value?.includes(option),
+      };
+    }),
+  });
   useAddFormElementToBootstrapContext(element);
   return <>{JSON.stringify(element)}</>;
 };
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/TextField.tsx 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/TextField.tsx
index 49db3122e17..a5c7204d4c4 100644
--- a/packages/form-code-generator-bootstrap4-theme/src/uniforms/TextField.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/src/uniforms/TextField.tsx
@@ -22,6 +22,7 @@ import { connectField, HTMLFieldProps } from "uniforms/cjs";
 import { INPUT, renderCodeGenElement } from "./templates/templates";
 import { useAddFormElementToBootstrapContext } from 
"./BootstrapCodeGenContext";
 import { FormInput } from "../api";
+import { ListItemProps } from "./rendering/ListFieldInput";
 
 export type TextFieldProps = HTMLFieldProps<
   string,
@@ -29,11 +30,12 @@ export type TextFieldProps = HTMLFieldProps<
   {
     label: string;
     required: boolean;
+    itemProps?: ListItemProps;
   }
 >;
 
 const Text: React.FC<TextFieldProps> = (props: TextFieldProps) => {
-  const properties = {
+  const element: FormInput = renderCodeGenElement(INPUT, {
     id: props.name,
     name: props.name,
     label: props.label,
@@ -42,9 +44,8 @@ const Text: React.FC<TextFieldProps> = (props: 
TextFieldProps) => {
     placeholder: props.placeholder,
     autoComplete: props.autoComplete ?? false,
     value: props.value,
-  };
-
-  const element: FormInput = renderCodeGenElement(INPUT, properties);
+    itemProps: props.itemProps,
+  });
   useAddFormElementToBootstrapContext(element);
   return <>{JSON.stringify(element)}</>;
 };
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/UnsupportedField.tsx
 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/UnsupportedField.tsx
index c2af65f0db8..e045ac57959 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/UnsupportedField.tsx
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/UnsupportedField.tsx
@@ -22,6 +22,7 @@ import { FormInput } from "../api";
 import { connectField, HTMLFieldProps } from "uniforms/cjs";
 import { renderCodeGenElement, UNSUPPORTED } from "./templates/templates";
 import { useAddFormElementToBootstrapContext } from 
"./BootstrapCodeGenContext";
+import { ListItemProps } from "./rendering/ListFieldInput";
 
 export type UnsupportedFieldProps = HTMLFieldProps<
   any,
@@ -29,18 +30,17 @@ export type UnsupportedFieldProps = HTMLFieldProps<
   {
     label: string;
     required: boolean;
+    itemProps: ListItemProps;
   }
 >;
 
 const Unsupported: React.FC<UnsupportedFieldProps> = (props: 
UnsupportedFieldProps) => {
-  const properties = {
-    id: props.id,
-    label: props.label,
+  const element: FormInput = renderCodeGenElement(UNSUPPORTED, {
+    id: props.name,
     name: props.name,
+    label: props.label,
     fieldType: props.fieldType.name,
-  };
-
-  const element: FormInput = renderCodeGenElement(UNSUPPORTED, properties);
+  });
   useAddFormElementToBootstrapContext(element);
   return <>{JSON.stringify(element)}</>;
 };
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/index.ts 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/index.ts
index 34dd64bb451..3f38e3c7ce8 100644
--- a/packages/form-code-generator-bootstrap4-theme/src/uniforms/index.ts
+++ b/packages/form-code-generator-bootstrap4-theme/src/uniforms/index.ts
@@ -23,6 +23,7 @@ export { default as AutoForm } from "./AutoForm";
 export { default as BoolField } from "./BoolField";
 export { default as CheckBoxGroupField } from "./CheckBoxGroupField";
 export { default as DateField } from "./DateField";
+export { default as ListField } from "./ListField";
 export { default as NestField } from "./NestField";
 export { default as NumField } from "./NumField";
 export { default as RadioField } from "./RadioField";
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/renderForm.tsx 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/renderForm.tsx
index 8f63ae0c5f2..bfe7f1dca79 100644
--- a/packages/form-code-generator-bootstrap4-theme/src/uniforms/renderForm.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/src/uniforms/renderForm.tsx
@@ -32,7 +32,5 @@ interface Args {
 }
 
 export function renderForm(args: Args): string {
-  const form = React.createElement(AutoForm, { ...args });
-
-  return unescape(ReactDOMServer.renderToString(form));
+  return unescape(ReactDOMServer.renderToString(React.createElement(AutoForm, 
{ ...args })));
 }
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/NestedFieldInput.tsx
 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/ListFieldInput.tsx
similarity index 76%
copy from 
packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/NestedFieldInput.tsx
copy to 
packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/ListFieldInput.tsx
index 2d80b4f32a2..92fc271aa84 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/NestedFieldInput.tsx
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/ListFieldInput.tsx
@@ -22,20 +22,26 @@ import * as React from "react";
 import AutoField from "../AutoField";
 import { BootstrapCodeGenContext, CodeGenContextProvider } from 
"../BootstrapCodeGenContext";
 
+export interface ListItemProps {
+  isListItem: boolean;
+  indexVariableName: string;
+  listName: string;
+}
+
 export interface Props {
   codegenCtx: BootstrapCodeGenContext;
   uniformsContext: Context<any>;
-  field: any;
-  itempProps: any;
+  fieldName: any;
+  itemProps: ListItemProps;
   disabled?: boolean;
 }
 
-export const NestedFieldInput: React.FC<Props> = ({ codegenCtx, 
uniformsContext, field, itempProps, disabled }) => {
+export const ListFieldInput: React.FC<Props> = ({ codegenCtx, uniformsContext, 
fieldName, itemProps, disabled }) => {
   return (
     <CodeGenContextProvider schema={uniformsContext.schema} 
codegenCtx={codegenCtx} uniformsCtx={uniformsContext}>
-      <AutoField key={field} name={field} disabled={disabled} {...itempProps} 
/>
+      <AutoField key={fieldName} name={fieldName} disabled={disabled} 
itemProps={itemProps} />
     </CodeGenContextProvider>
   );
 };
 
-export default NestedFieldInput;
+export default ListFieldInput;
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/NestedFieldInput.tsx
 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/NestedFieldInput.tsx
index 2d80b4f32a2..8c1a9877560 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/NestedFieldInput.tsx
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/NestedFieldInput.tsx
@@ -21,19 +21,20 @@ import { Context } from "uniforms";
 import * as React from "react";
 import AutoField from "../AutoField";
 import { BootstrapCodeGenContext, CodeGenContextProvider } from 
"../BootstrapCodeGenContext";
+import { ListItemProps } from "./ListFieldInput";
 
 export interface Props {
   codegenCtx: BootstrapCodeGenContext;
   uniformsContext: Context<any>;
   field: any;
-  itempProps: any;
+  itemProps: ListItemProps;
   disabled?: boolean;
 }
 
-export const NestedFieldInput: React.FC<Props> = ({ codegenCtx, 
uniformsContext, field, itempProps, disabled }) => {
+export const NestedFieldInput: React.FC<Props> = ({ codegenCtx, 
uniformsContext, field, itemProps, disabled }) => {
   return (
     <CodeGenContextProvider schema={uniformsContext.schema} 
codegenCtx={codegenCtx} uniformsCtx={uniformsContext}>
-      <AutoField key={field} name={field} disabled={disabled} {...itempProps} 
/>
+      <AutoField key={field} name={field} disabled={disabled} 
itemProps={itemProps} />
     </CodeGenContextProvider>
   );
 };
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/RenderingUtils.tsx
 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/RenderingUtils.tsx
index a4218892f76..ed3e8cff4bb 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/RenderingUtils.tsx
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/RenderingUtils.tsx
@@ -24,6 +24,7 @@ import { FormElement, FormInput } from "../../api";
 import FormInputs from "./FormInputs";
 import NestedFieldInput from "./NestedFieldInput";
 import { BootstrapCodeGenContext } from "../BootstrapCodeGenContext";
+import ListFieldInput, { ListItemProps } from "./ListFieldInput";
 
 export const renderFormInputs = (schema: Bridge): FormElement<any>[] => {
   const codegenCtx: BootstrapCodeGenContext = {
@@ -36,14 +37,13 @@ export const renderFormInputs = (schema: Bridge): 
FormElement<any>[] => {
   });
 
   ReactDOMServer.renderToString(inputsElement);
-
   return codegenCtx.rendered;
 };
 
 export const renderNestedInputFragmentWithContext = (
   uniformsContext: any,
   field: any,
-  itempProps: any,
+  itemProps: ListItemProps,
   disabled?: boolean
 ): FormInput | undefined => {
   const codegenCtx: BootstrapCodeGenContext = {
@@ -55,7 +55,30 @@ export const renderNestedInputFragmentWithContext = (
       codegenCtx,
       uniformsContext,
       field,
-      itempProps,
+      itemProps,
+      disabled,
+    })
+  );
+
+  return codegenCtx.rendered.length === 1 ? codegenCtx.rendered[0] : undefined;
+};
+
+export const renderListItemFragmentWithContext = (
+  uniformsContext: any,
+  fieldName: string,
+  itemProps: ListItemProps,
+  disabled?: boolean
+): FormInput | undefined => {
+  const codegenCtx: BootstrapCodeGenContext = {
+    rendered: [],
+  };
+
+  ReactDOMServer.renderToString(
+    React.createElement(ListFieldInput, {
+      codegenCtx,
+      uniformsContext,
+      fieldName,
+      itemProps,
       disabled,
     })
   );
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/AbstractFormGroupTemplate.ts
 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/AbstractFormGroupTemplate.ts
new file mode 100644
index 00000000000..e6eba8530a8
--- /dev/null
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/AbstractFormGroupTemplate.ts
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import formGroupTemplate from 
"!!raw-loader!../../resources/templates/formGroup.template";
+import { CodeGenElement, FormElement, FormInput } from "../../api";
+import { CompiledTemplate, template } from "underscore";
+import { getInputReference } from "../utils/Utils";
+import { fieldNameToOptionalChain, flatFieldName, getItemValeuPath } from 
"./utils";
+import { ListItemProps } from "../rendering/ListFieldInput";
+import { DEFAULT_LIST_INDEX_NAME, getCurrentItemSetModelData, 
getNormalizedListIdOrName } from "./ListFieldTemplate";
+
+export interface CodeGenTemplate<Element extends CodeGenElement, Properties> {
+  render: (props: Properties) => Element;
+}
+
+export interface FormElementTemplateProps<Type> {
+  id: string;
+  name: string;
+  label: string;
+  disabled: boolean;
+  value: Type;
+  itemProps: ListItemProps;
+  type: string;
+  placeholder?: string;
+  autoComplete?: boolean;
+  min?: string | number;
+  max?: string | number;
+  step?: number;
+}
+
+export interface FormElementTemplate<
+  Element extends FormElement<any>,
+  Properties extends FormElementTemplateProps<any>,
+> {
+  render: (props: Properties) => Element;
+}
+
+export abstract class AbstractFormGroupTemplate<Properties extends 
FormElementTemplateProps<any>>
+  implements FormElementTemplate<FormInput, Properties>
+{
+  protected constructor(
+    readonly inputTemplate: CompiledTemplate,
+    readonly setValueFromModelTemplate: CompiledTemplate,
+    readonly writeValueToModelTemplate: CompiledTemplate
+  ) {}
+
+  render(props: Properties): FormInput {
+    return {
+      ref: getInputReference(props),
+      html: template(formGroupTemplate)({
+        id: props.itemProps?.isListItem ? getNormalizedListIdOrName(props.id) 
: props.id,
+        label: props.label,
+        input: this.inputTemplate({
+          id: props.itemProps?.isListItem ? 
getNormalizedListIdOrName(props.id) : props.id,
+          name: props.itemProps?.isListItem ? 
getNormalizedListIdOrName(props.name) : props.name,
+          value: props.value,
+          type: props.type,
+          disabled: props.disabled,
+          placeholder: props.placeholder,
+          autoComplete: props.autoComplete,
+          min: props.min,
+          max: props.max,
+          step: props.step,
+        }),
+        isListItem: props.itemProps?.isListItem ?? false,
+      }),
+      disabled: props.disabled,
+      globalFunctions: undefined,
+      setValueFromModelCode: {
+        code: this.setValueFromModelTemplate({
+          ...props,
+          id: props.itemProps?.isListItem
+            ? getCurrentItemSetModelData(props.id, 
props.itemProps?.indexVariableName ?? DEFAULT_LIST_INDEX_NAME)
+            : props.id,
+          isListItem: props.itemProps?.isListItem ?? false,
+          path: fieldNameToOptionalChain(props.name),
+          valuePath: props.itemProps?.isListItem ? 
getItemValeuPath(props.name) : "",
+          flatFieldName: flatFieldName(props.name),
+        }),
+      },
+      writeValueToModelCode:
+        props.disabled || !this.writeValueToModelTemplate
+          ? undefined
+          : {
+              code: this.writeValueToModelTemplate({
+                ...props,
+                flatFieldName: flatFieldName(props.name),
+              }),
+            },
+    };
+  }
+}
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/AutoFormTemplate.ts
 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/AutoFormTemplate.ts
index 925f33a5fd3..c9da6140575 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/AutoFormTemplate.ts
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/AutoFormTemplate.ts
@@ -20,7 +20,7 @@
 import form from "!!raw-loader!../../resources/templates/form.template";
 import * as prettier from "prettier";
 import trim from "lodash/trim";
-import { CodeGenTemplate } from "./types";
+import { CodeGenTemplate } from "./AbstractFormGroupTemplate";
 import { CompiledTemplate, template } from "underscore";
 import { CodeGenElement, FormElement } from "../../api";
 
@@ -36,12 +36,8 @@ export class AutoFormTemplate implements 
CodeGenTemplate<CodeGenElement, AutoFor
   }
 
   render(props: AutoFormProps): CodeGenElement {
-    const data = {
-      props: props,
-    };
-
     const rawTemplate = trim(
-      this.formTemplate(data)
+      this.formTemplate({ props })
         .split("\n")
         .filter((line) => line && line.trim().length > 0)
         .join("\n")
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/BoolFieldTemplate.ts
 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/BoolFieldTemplate.ts
index 5f1fdde7782..2bd42fd5948 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/BoolFieldTemplate.ts
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/BoolFieldTemplate.ts
@@ -20,11 +20,12 @@
 import checkbox from 
"!!raw-loader!../../resources/templates/checkbox.template";
 import checkboxSetValueFromModel from 
"!!raw-loader!../../resources/templates/checkbox.setModelData.template";
 import checkboxWriteModelData from 
"!!raw-loader!../../resources/templates/checkbox.writeModelData.template";
-import { FormElementTemplate, FormElementTemplateProps } from "./types";
-import { CodeFragment, FormInput } from "../../api";
+import { FormElementTemplate, FormElementTemplateProps } from 
"./AbstractFormGroupTemplate";
+import { FormInput } from "../../api";
 import { CompiledTemplate, template } from "underscore";
 import { getInputReference } from "../utils/Utils";
-import { fieldNameToOptionalChain } from "./utils";
+import { fieldNameToOptionalChain, getItemValeuPath } from "./utils";
+import { DEFAULT_LIST_INDEX_NAME, getCurrentItemSetModelData, 
getNormalizedListIdOrName } from "./ListFieldTemplate";
 
 interface BoolFieldProps extends FormElementTemplateProps<boolean> {
   checked: boolean;
@@ -42,38 +43,35 @@ export class BoolFieldTemplate implements 
FormElementTemplate<FormInput, BoolFie
   }
 
   render(props: BoolFieldProps): FormInput {
-    const data = {
-      props: props,
-    };
     return {
       ref: getInputReference(props),
-      html: this.checkboxTemplate(data),
+      html: this.checkboxTemplate({
+        id: props.itemProps?.isListItem ? getNormalizedListIdOrName(props.id) 
: props.id,
+        name: props.itemProps?.isListItem ? 
getNormalizedListIdOrName(props.name) : props.name,
+        disabled: props.disabled,
+        checked: props.checked,
+        label: props.label,
+      }),
       disabled: props.disabled,
-      setValueFromModelCode: this.buildSetValueFromModelCode(props),
-      writeValueToModelCode: this.buildWriteModelDataCode(props),
-    };
-  }
-
-  protected buildSetValueFromModelCode(props: BoolFieldProps): CodeFragment {
-    const properties = {
-      id: props.id,
-      path: fieldNameToOptionalChain(props.name),
-    };
-    return {
-      code: this.checkboxSetValueFromModelTemplate(properties),
-    };
-  }
-
-  protected buildWriteModelDataCode(props: BoolFieldProps): CodeFragment | 
undefined {
-    if (props.disabled) {
-      return undefined;
-    }
-    const properties = {
-      id: props.id,
-      name: props.name,
-    };
-    return {
-      code: this.checkboxWriteModelTemplate(properties),
+      globalFunctions: undefined,
+      setValueFromModelCode: {
+        code: this.checkboxSetValueFromModelTemplate({
+          id: props.itemProps?.isListItem
+            ? getCurrentItemSetModelData(props.id, 
props.itemProps?.indexVariableName ?? DEFAULT_LIST_INDEX_NAME)
+            : props.id,
+          path: fieldNameToOptionalChain(props.name),
+          valuePath: props.itemProps?.isListItem ? 
getItemValeuPath(props.name) : "",
+          isListItem: props.itemProps?.isListItem ?? false,
+        }),
+      },
+      writeValueToModelCode: props.disabled
+        ? undefined
+        : {
+            code: this.checkboxWriteModelTemplate({
+              id: props.id,
+              name: props.name,
+            }),
+          },
     };
   }
 }
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/CheckboxGroupFieldTemplate.ts
 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/CheckboxGroupFieldTemplate.ts
index 256af1d5275..e9d39d233a3 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/CheckboxGroupFieldTemplate.ts
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/CheckboxGroupFieldTemplate.ts
@@ -22,11 +22,13 @@ import getRequiredCode from 
"!!raw-loader!../../resources/staticCode/getCheckbox
 import input from 
"!!raw-loader!../../resources/templates/checkboxGroup.template";
 import setValueFromModel from 
"!!raw-loader!../../resources/templates/checkboxGroup.setModelData.template";
 import writeValueToModel from 
"!!raw-loader!../../resources/templates/checkboxGroup.writeModelData.template";
-import { FORM_GROUP_TEMPLATE, FormElementTemplate, FormElementTemplateProps } 
from "./types";
+import formGroupTemplate from 
"!!raw-loader!../../resources/templates/formGroup.template";
+import { FormElementTemplate, FormElementTemplateProps } from 
"./AbstractFormGroupTemplate";
 import { CompiledTemplate, template } from "underscore";
-import { CodeFragment, FormInput } from "../../api";
-import { fieldNameToOptionalChain, flatFieldName } from "./utils";
+import { FormInput } from "../../api";
+import { fieldNameToOptionalChain, getItemValeuPath } from "./utils";
 import { getInputReference } from "../utils/Utils";
+import { DEFAULT_LIST_INDEX_NAME, getCurrentItemSetModelData, 
getNormalizedListIdOrName } from "./ListFieldTemplate";
 
 export interface Option {
   value: string;
@@ -42,47 +44,49 @@ export class CheckBoxGroupFieldTemplate implements 
FormElementTemplate<FormInput
   private readonly inputTemplate: CompiledTemplate;
   private readonly setValueFromModelTemplate: CompiledTemplate;
   private readonly writeValueToModelTemplate: CompiledTemplate;
+  private readonly formGroupTemplate: CompiledTemplate;
 
   constructor() {
     this.inputTemplate = template(input);
     this.setValueFromModelTemplate = template(setValueFromModel);
     this.writeValueToModelTemplate = template(writeValueToModel);
+    this.formGroupTemplate = template(formGroupTemplate);
   }
 
   render(props: CheckBoxGroupFieldProps): FormInput {
-    const data = {
-      props: props,
-      input: this.inputTemplate({ props: props }),
-    };
-
     return {
       ref: getInputReference(props),
-      html: FORM_GROUP_TEMPLATE(data),
+      html: this.formGroupTemplate({
+        id: props.itemProps?.isListItem ? getNormalizedListIdOrName(props.id) 
: props.id,
+        label: props.label,
+        input: this.inputTemplate({
+          id: props.itemProps?.isListItem ? 
getNormalizedListIdOrName(props.id) : props.id,
+          name: props.itemProps?.isListItem ? 
getNormalizedListIdOrName(props.name) : props.name,
+          options: props.options,
+          disabled: props.disabled,
+        }),
+        isListItem: props.itemProps?.isListItem ?? false,
+      }),
       disabled: props.disabled,
-      setValueFromModelCode: this.buildSetValueFromModelCode(props),
-      writeValueToModelCode: this.buildWriteValueToModelCode(props),
-    };
-  }
-
-  private buildSetValueFromModelCode(props: CheckBoxGroupFieldProps): 
CodeFragment {
-    const properties = {
-      ...props,
-      path: fieldNameToOptionalChain(props.name),
-    };
-    return {
-      code: this.setValueFromModelTemplate(properties),
-      requiredCode: [setRequiredCode],
-    };
-  }
-
-  private buildWriteValueToModelCode(props: CheckBoxGroupFieldProps): 
CodeFragment | undefined {
-    if (props.disabled) {
-      return undefined;
-    }
-
-    return {
-      code: this.writeValueToModelTemplate(props),
-      requiredCode: [getRequiredCode],
+      globalFunctions: undefined,
+      setValueFromModelCode: {
+        code: this.setValueFromModelTemplate({
+          ...props,
+          name: props.itemProps?.isListItem
+            ? getCurrentItemSetModelData(props.name, 
props.itemProps?.indexVariableName ?? DEFAULT_LIST_INDEX_NAME)
+            : props.name,
+          path: fieldNameToOptionalChain(props.name),
+          valuePath: props.itemProps?.isListItem ? 
getItemValeuPath(props.name) : "",
+          isListItem: props.itemProps?.isListItem ?? false,
+        }),
+        requiredCode: [setRequiredCode],
+      },
+      writeValueToModelCode: props.disabled
+        ? undefined
+        : {
+            code: this.writeValueToModelTemplate(props),
+            requiredCode: [getRequiredCode],
+          },
     };
   }
 }
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/DateFieldTemplate.ts
 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/DateFieldTemplate.ts
index ac96bb532ce..264c27243b1 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/DateFieldTemplate.ts
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/DateFieldTemplate.ts
@@ -21,7 +21,7 @@ import date from 
"!!raw-loader!../../resources/templates/date.template";
 import setValueFromModel from 
"!!raw-loader!../../resources/templates/date.setModelData.template";
 import writeValueToModel from 
"!!raw-loader!../../resources/templates/input.writeModelData.template";
 import { template } from "underscore";
-import { AbstractFormGroupInputTemplate, FormElementTemplateProps } from 
"./types";
+import { AbstractFormGroupTemplate, FormElementTemplateProps } from 
"./AbstractFormGroupTemplate";
 
 interface DateFieldProps extends FormElementTemplateProps<string> {
   type: string;
@@ -31,7 +31,7 @@ interface DateFieldProps extends 
FormElementTemplateProps<string> {
   min: string;
 }
 
-export class DateFieldTemplate extends 
AbstractFormGroupInputTemplate<DateFieldProps> {
+export class DateFieldTemplate extends 
AbstractFormGroupTemplate<DateFieldProps> {
   constructor() {
     super(template(date), template(setValueFromModel), 
template(writeValueToModel));
   }
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/ListFieldTemplate.ts
 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/ListFieldTemplate.ts
new file mode 100644
index 00000000000..6ece259d7cd
--- /dev/null
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/ListFieldTemplate.ts
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import listField from 
"!!raw-loader!../../resources/templates/listField.template";
+import globalFunctions from 
"!!raw-loader!../../resources/templates/listField.globalFunctions.template";
+import setValueFromModel from 
"!!raw-loader!../../resources/templates/listField.setModelData.template";
+import writeValueToModel from 
"!!raw-loader!../../resources/templates/listField.writeModelData.template";
+import listHelperFunctions from 
"!!raw-loader!../../resources/staticCode/listHelperFunctions.txt";
+import { FormElementTemplate, FormElementTemplateProps } from 
"./AbstractFormGroupTemplate";
+import { FormElement, FormInputContainer, InputReference } from "../../api";
+import { CompiledTemplate, template } from "underscore";
+import { fieldNameToOptionalChain } from "./utils";
+import { ListItemProps } from "../rendering/ListFieldInput";
+
+export const DEFAULT_LIST_INDEX_NAME = "itemIndex";
+
+interface ListFieldTemplateProps extends FormElementTemplateProps<any> {
+  children: FormElement<any>;
+  maxCount: number;
+  minCount: number;
+}
+
+export class ListFieldTemplate implements 
FormElementTemplate<FormInputContainer, ListFieldTemplateProps> {
+  private readonly listFieldTemplate: CompiledTemplate = template(listField);
+  private readonly listFieldGlobalFunctionsTemplate: CompiledTemplate = 
template(globalFunctions);
+  private readonly listFieldSetValueFromModelTemplate: CompiledTemplate = 
template(setValueFromModel);
+  private readonly listFieldWriteValueToModelTemplate: CompiledTemplate = 
template(writeValueToModel);
+
+  render({
+    id,
+    disabled,
+    label,
+    children,
+    maxCount,
+    minCount,
+    value,
+    name,
+    itemProps,
+  }: ListFieldTemplateProps): FormInputContainer {
+    const ref: InputReference[] = children.ref;
+
+    const getDefaultItemValue = () => {
+      return "undefined";
+    };
+
+    return {
+      ref,
+      html: this.listFieldTemplate({
+        id: getNormalizedListIdOrName(id),
+        disabled,
+        label,
+        value,
+        childrenHtml: children.html,
+        functionName: getFunctionName(name),
+      }),
+      disabled: disabled,
+      globalFunctions: {
+        code:
+          this.listFieldGlobalFunctionsTemplate({
+            defaultValue: getDefaultItemValue(),
+            disabled,
+            minCount,
+            maxCount,
+            childrenHtml: `${children.html}`,
+            name,
+            functionName: getFunctionName(name),
+          }) + (children?.globalFunctions?.code !== undefined ? 
children?.globalFunctions?.code : ""),
+        requiredCode: [listHelperFunctions],
+      },
+      setValueFromModelCode: {
+        code: this.listFieldSetValueFromModelTemplate({
+          id,
+          path: getSetItemPath(name),
+          indexVariableName: "index",
+          itemsSetValueFromModel: children.setValueFromModelCode,
+          name: getCurrentItemSetModelData(name, itemProps?.indexVariableName 
?? DEFAULT_LIST_INDEX_NAME),
+          functionName: getFunctionName(name),
+          prefix: getNextIndexVariableName(itemProps),
+        }),
+        requiredCode: [],
+      },
+      writeValueToModelCode: {
+        code: this.listFieldWriteValueToModelTemplate({
+          disabled,
+          name,
+        }),
+        requiredCode: [],
+      },
+    };
+  }
+}
+
+// "value" is the name used in the listField.setModelData.template
+function getSetItemPath(name: string) {
+  if (name.endsWith("$")) {
+    return "value";
+  }
+  if (name.includes("$.")) {
+    return `value?.${name.split(".").pop()}`;
+  }
+  return `data?.${fieldNameToOptionalChain(name)}`;
+}
+
+// "currentItem" is the name used in the listField.setModelData.template
+export function getCurrentItemSetModelData(name: string, prefix: string) {
+  const splittedName = name.split(`.$`);
+  // Is nested
+  if (splittedName.length > 1) {
+    // ${currentItem}. with last part of the field name
+    return `\${${prefix}__currentItem}` + (splittedName?.[splittedName.length 
- 1] ?? "");
+  }
+  return name;
+}
+
+function getFunctionName(name: string) {
+  return name
+    .split(".")
+    .map((word) => `${word?.[0]?.toUpperCase()}${word?.slice(1)}`)
+    .join("");
+}
+
+export function getNextIndexVariableName(itemProps: ListItemProps) {
+  if (itemProps === undefined) {
+    return DEFAULT_LIST_INDEX_NAME;
+  }
+  return "nested__" + itemProps.indexVariableName;
+}
+
+function splitLastOccurrence(str: string, char: string) {
+  const lastIndex = str.lastIndexOf(char);
+  if (lastIndex === -1) {
+    return [str];
+  }
+  return [str.substring(0, lastIndex), str.substring(lastIndex)];
+}
+
+export function getNormalizedListIdOrName(id: string) {
+  return `${splitLastOccurrence(id, "$")[1] ? 
`\${name}.${splitLastOccurrence(id, "$")[1]}` : id}`;
+}
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/NestFieldTemplate.ts
 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/NestFieldTemplate.ts
index 51550932263..920d022fb7a 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/NestFieldTemplate.ts
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/NestFieldTemplate.ts
@@ -18,10 +18,11 @@
  */
 
 import nestField from 
"!!raw-loader!../../resources/templates/nestField.template";
+import globalFunctions from 
"!!raw-loader!../../resources/templates/nestField.globalFunctions.template";
 import setValueFromModel from 
"!!raw-loader!../../resources/templates/nestField.setModelData.template";
 import writeValueToModel from 
"!!raw-loader!../../resources/templates/nestField.writeModelData.template";
-import { FormElementTemplate, FormElementTemplateProps } from "./types";
-import { CodeFragment, FormElement, FormInputContainer, InputReference } from 
"../../api";
+import { FormElementTemplate, FormElementTemplateProps } from 
"./AbstractFormGroupTemplate";
+import { FormElement, FormInputContainer, InputReference } from "../../api";
 import { CompiledTemplate, template } from "underscore";
 import { union } from "lodash";
 
@@ -31,12 +32,14 @@ interface NestFieldTemplateProps extends 
FormElementTemplateProps<any> {
 
 export class NestFieldTemplate implements 
FormElementTemplate<FormInputContainer, NestFieldTemplateProps> {
   private readonly nestFieldTemplate: CompiledTemplate = template(nestField);
+  private readonly nestFieldGlobalFunctionsTemplate: CompiledTemplate = 
template(globalFunctions);
   private readonly nestFieldSetValueFromModelTemplate: CompiledTemplate = 
template(setValueFromModel);
   private readonly nestFieldWriteValueToModelTemplate: CompiledTemplate = 
template(writeValueToModel);
 
   render(props: NestFieldTemplateProps): FormInputContainer {
     const ref: InputReference[] = [];
 
+    let globalFunctionsRequiredCode: string[] = [];
     let setValueFromModelRequiredCode: string[] = [];
     let writeValueToModelRequiredCode: string[] = [];
 
@@ -47,6 +50,10 @@ export class NestFieldTemplate implements 
FormElementTemplate<FormInputContainer
         ref.push(child.ref);
       }
 
+      if (child.globalFunctions) {
+        globalFunctionsRequiredCode = union(globalFunctionsRequiredCode, 
child.globalFunctions.requiredCode);
+      }
+
       if (child.setValueFromModelCode) {
         setValueFromModelRequiredCode = union(setValueFromModelRequiredCode, 
child.setValueFromModelCode.requiredCode);
       }
@@ -60,31 +67,20 @@ export class NestFieldTemplate implements 
FormElementTemplate<FormInputContainer
       ref,
       html: this.nestFieldTemplate({ props: props }),
       disabled: props.disabled,
-      setValueFromModelCode: this.buildSetValueFromModelCode(props, 
setValueFromModelRequiredCode),
-      writeValueToModelCode: this.buildWriteValueFromModelCode(props, 
writeValueToModelRequiredCode),
-    };
-  }
-
-  protected buildSetValueFromModelCode(
-    props: NestFieldTemplateProps,
-    setValueFromModelRequiredCode: string[]
-  ): CodeFragment {
-    return {
-      code: this.nestFieldSetValueFromModelTemplate({ props: props }),
-      requiredCode: setValueFromModelRequiredCode,
-    };
-  }
-
-  protected buildWriteValueFromModelCode(
-    props: NestFieldTemplateProps,
-    writeValueToModelRequiredCode: string[]
-  ): CodeFragment | undefined {
-    if (props.disabled) {
-      return undefined;
-    }
-    return {
-      code: this.nestFieldWriteValueToModelTemplate({ props: props }),
-      requiredCode: writeValueToModelRequiredCode,
+      globalFunctions: {
+        code: this.nestFieldGlobalFunctionsTemplate({ props: props }),
+        requiredCode: globalFunctionsRequiredCode,
+      },
+      setValueFromModelCode: {
+        code: this.nestFieldSetValueFromModelTemplate({ props: props }),
+        requiredCode: setValueFromModelRequiredCode,
+      },
+      writeValueToModelCode: props.disabled
+        ? undefined
+        : {
+            code: this.nestFieldWriteValueToModelTemplate({ props: props }),
+            requiredCode: writeValueToModelRequiredCode,
+          },
     };
   }
 }
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/NumFieldTemplate.ts
 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/NumFieldTemplate.ts
index 6993b3cd8d1..b0340bd6552 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/NumFieldTemplate.ts
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/NumFieldTemplate.ts
@@ -21,7 +21,7 @@ import number from 
"!!raw-loader!../../resources/templates/number.template";
 import setValueFromModel from 
"!!raw-loader!../../resources/templates/input.setModelData.template";
 import writeValueToModel from 
"!!raw-loader!../../resources/templates/number.writeModelData.template";
 import { template } from "underscore";
-import { AbstractFormGroupInputTemplate, FormElementTemplateProps } from 
"./types";
+import { AbstractFormGroupTemplate, FormElementTemplateProps } from 
"./AbstractFormGroupTemplate";
 
 interface NumFieldProps extends FormElementTemplateProps<string> {
   autoComplete: boolean;
@@ -31,7 +31,7 @@ interface NumFieldProps extends 
FormElementTemplateProps<string> {
   step: number;
 }
 
-export class NumFieldTemplate extends 
AbstractFormGroupInputTemplate<NumFieldProps> {
+export class NumFieldTemplate extends AbstractFormGroupTemplate<NumFieldProps> 
{
   constructor() {
     super(template(number), template(setValueFromModel), 
template(writeValueToModel));
   }
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/RadioGroupFieldTemplate.ts
 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/RadioGroupFieldTemplate.ts
index 980b6b1bd20..fc10c56c62b 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/RadioGroupFieldTemplate.ts
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/RadioGroupFieldTemplate.ts
@@ -22,16 +22,13 @@ import getRequiredCode from 
"!!raw-loader!../../resources/staticCode/getRadioGro
 import input from "!!raw-loader!../../resources/templates/radioGroup.template";
 import setValueFromModel from 
"!!raw-loader!../../resources/templates/radioGroup.setModelData.template";
 import writeValueToModel from 
"!!raw-loader!../../resources/templates/radioGroup.writeModelData.template";
-import {
-  AbstractFormGroupInputTemplate,
-  FORM_GROUP_TEMPLATE,
-  FormElementTemplate,
-  FormElementTemplateProps,
-} from "./types";
+import formGroupTemplate from 
"!!raw-loader!../../resources/templates/formGroup.template";
+import { FormElementTemplate, FormElementTemplateProps } from 
"./AbstractFormGroupTemplate";
 import { CompiledTemplate, template } from "underscore";
-import { CodeFragment, FormInput } from "../../api";
-import { fieldNameToOptionalChain, flatFieldName } from "./utils";
+import { FormInput } from "../../api";
+import { fieldNameToOptionalChain, getItemValeuPath } from "./utils";
 import { getInputReference } from "../utils/Utils";
+import { DEFAULT_LIST_INDEX_NAME, getCurrentItemSetModelData, 
getNormalizedListIdOrName } from "./ListFieldTemplate";
 
 export interface Option {
   value: string;
@@ -47,48 +44,49 @@ export class RadioGroupFieldTemplate implements 
FormElementTemplate<FormInput, R
   private readonly inputTemplate: CompiledTemplate;
   private readonly setValueFromModelTemplate: CompiledTemplate;
   private readonly writeValueToModelTemplate: CompiledTemplate;
+  private readonly formGroupTemplate: CompiledTemplate;
 
   constructor() {
     this.inputTemplate = template(input);
     this.setValueFromModelTemplate = template(setValueFromModel);
     this.writeValueToModelTemplate = template(writeValueToModel);
+    this.formGroupTemplate = template(formGroupTemplate);
   }
 
   render(props: RadioGroupFieldProps): FormInput {
-    const data = {
-      props: props,
-      input: this.inputTemplate({ props: props }),
-    };
-
     return {
       ref: getInputReference(props),
-      html: FORM_GROUP_TEMPLATE(data),
+      html: this.formGroupTemplate({
+        id: props.itemProps?.isListItem ? getNormalizedListIdOrName(props.id) 
: props.id,
+        label: props.label,
+        input: this.inputTemplate({
+          id: props.itemProps?.isListItem ? 
getNormalizedListIdOrName(props.id) : props.id,
+          name: props.itemProps?.isListItem ? 
getNormalizedListIdOrName(props.name) : props.name,
+          options: props.options,
+          disabled: props.disabled,
+        }),
+        isListItem: props.itemProps?.isListItem ?? false,
+      }),
       disabled: props.disabled,
-      setValueFromModelCode: this.buildSetValueFromModelCode(props),
-      writeValueToModelCode: this.writeValueToModelCode(props),
-    };
-  }
-
-  protected buildSetValueFromModelCode(props: RadioGroupFieldProps): 
CodeFragment {
-    const properties = {
-      ...props,
-      path: fieldNameToOptionalChain(props.name),
-    };
-
-    return {
-      requiredCode: [setRequiredCode],
-      code: this.setValueFromModelTemplate(properties),
-    };
-  }
-
-  protected writeValueToModelCode(props: RadioGroupFieldProps): CodeFragment | 
undefined {
-    if (props.disabled) {
-      return undefined;
-    }
-
-    return {
-      requiredCode: [getRequiredCode],
-      code: this.writeValueToModelTemplate(props),
+      globalFunctions: undefined,
+      setValueFromModelCode: {
+        requiredCode: [setRequiredCode],
+        code: this.setValueFromModelTemplate({
+          ...props,
+          name: props.itemProps?.isListItem
+            ? getCurrentItemSetModelData(props.name, 
props.itemProps?.indexVariableName ?? DEFAULT_LIST_INDEX_NAME)
+            : props.name,
+          path: fieldNameToOptionalChain(props.name),
+          valuePath: props.itemProps?.isListItem ? 
getItemValeuPath(props.name) : "",
+          isListItem: props.itemProps?.isListItem ?? false,
+        }),
+      },
+      writeValueToModelCode: props.disabled
+        ? undefined
+        : {
+            requiredCode: [getRequiredCode],
+            code: this.writeValueToModelTemplate(props),
+          },
     };
   }
 }
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/SelectFieldTemplate.ts
 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/SelectFieldTemplate.ts
index 9d2e13a2645..325b95dcbbd 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/SelectFieldTemplate.ts
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/SelectFieldTemplate.ts
@@ -23,11 +23,13 @@ import getMultipleRequiredCode from 
"!!raw-loader!../../resources/staticCode/get
 import input from "!!raw-loader!../../resources/templates/select.template";
 import setValueFromModel from 
"!!raw-loader!../../resources/templates/select.setModelData.template";
 import writeValueToModel from 
"!!raw-loader!../../resources/templates/select.writeModelData.template";
-import { FORM_GROUP_TEMPLATE, FormElementTemplate, FormElementTemplateProps } 
from "./types";
+import formGroupTemplate from 
"!!raw-loader!../../resources/templates/formGroup.template";
+import { FormElementTemplate, FormElementTemplateProps } from 
"./AbstractFormGroupTemplate";
 import { CompiledTemplate, template } from "underscore";
-import { CodeFragment, FormInput } from "../../api";
-import { fieldNameToOptionalChain } from "./utils";
+import { FormInput } from "../../api";
+import { fieldNameToOptionalChain, getItemValeuPath } from "./utils";
 import { getInputReference } from "../utils/Utils";
+import { DEFAULT_LIST_INDEX_NAME, getCurrentItemSetModelData, 
getNormalizedListIdOrName } from "./ListFieldTemplate";
 
 export interface Option {
   value: string;
@@ -45,47 +47,53 @@ export class SelectFieldTemplate implements 
FormElementTemplate<FormInput, Selec
   private readonly inputTemplate: CompiledTemplate;
   private readonly setValueFromModelTemplate: CompiledTemplate;
   private readonly writeValueToModelTemplate: CompiledTemplate;
+  private readonly formGroupTemplate: CompiledTemplate;
 
   constructor() {
     this.inputTemplate = template(input);
     this.setValueFromModelTemplate = template(setValueFromModel);
     this.writeValueToModelTemplate = template(writeValueToModel);
+    this.formGroupTemplate = template(formGroupTemplate);
   }
 
   render(props: SelectFieldProps): FormInput {
-    const data = {
-      props: props,
-      input: this.inputTemplate({ props: props }),
-    };
-
     return {
       ref: getInputReference(props),
-      html: FORM_GROUP_TEMPLATE(data),
+      html: this.formGroupTemplate({
+        id: props.itemProps?.isListItem ? getNormalizedListIdOrName(props.id) 
: props.id,
+        label: props.label,
+        input: this.inputTemplate({
+          id: props.itemProps?.isListItem ? 
getNormalizedListIdOrName(props.id) : props.id,
+          name: props.itemProps?.isListItem ? 
getNormalizedListIdOrName(props.name) : props.name,
+          placeHolder: props.placeHolder,
+          disabled: props.disabled,
+          multiple: props.multiple,
+          options: props.options,
+          value: props.value,
+          label: props.label,
+        }),
+        isListItem: props.itemProps?.isListItem ?? false,
+      }),
       disabled: props.disabled,
-      setValueFromModelCode: this.buildSetValueFromModelCode(props),
-      writeValueToModelCode: this.writeValueToModelCode(props),
-    };
-  }
-
-  protected buildSetValueFromModelCode(props: SelectFieldProps): CodeFragment {
-    const properties = {
-      ...props,
-      path: fieldNameToOptionalChain(props.name),
-    };
-    return {
-      code: this.setValueFromModelTemplate(properties),
-      requiredCode: props.multiple ? [setMultipleRequiredCode] : 
[setRequiredCode],
-    };
-  }
-
-  protected writeValueToModelCode(props: SelectFieldProps): CodeFragment | 
undefined {
-    if (props.disabled) {
-      return undefined;
-    }
-
-    return {
-      requiredCode: props.multiple ? [getMultipleRequiredCode] : undefined,
-      code: this.writeValueToModelTemplate(props),
+      globalFunctions: undefined,
+      setValueFromModelCode: {
+        code: this.setValueFromModelTemplate({
+          ...props,
+          id: props.itemProps?.isListItem
+            ? getCurrentItemSetModelData(props.id, 
props.itemProps?.indexVariableName ?? DEFAULT_LIST_INDEX_NAME)
+            : props.id,
+          path: fieldNameToOptionalChain(props.name),
+          valuePath: props.itemProps?.isListItem ? 
getItemValeuPath(props.name) : "",
+          isListItem: props.itemProps?.isListItem ?? false,
+        }),
+        requiredCode: props.multiple ? [setMultipleRequiredCode] : 
[setRequiredCode],
+      },
+      writeValueToModelCode: props.disabled
+        ? undefined
+        : {
+            requiredCode: props.multiple ? [getMultipleRequiredCode] : 
undefined,
+            code: this.writeValueToModelTemplate(props),
+          },
     };
   }
 }
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/TextFieldTemplate.ts
 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/TextFieldTemplate.ts
index 1bafd335e32..90893307e50 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/TextFieldTemplate.ts
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/TextFieldTemplate.ts
@@ -21,7 +21,7 @@ import input from 
"!!raw-loader!../../resources/templates/input.template";
 import inputBindModelData from 
"!!raw-loader!../../resources/templates/input.setModelData.template";
 import inputWriteModelData from 
"!!raw-loader!../../resources/templates/input.writeModelData.template";
 import { template } from "underscore";
-import { AbstractFormGroupInputTemplate, FormElementTemplateProps } from 
"./types";
+import { AbstractFormGroupTemplate, FormElementTemplateProps } from 
"./AbstractFormGroupTemplate";
 
 interface TextFieldProps extends FormElementTemplateProps<string> {
   type: string;
@@ -29,7 +29,7 @@ interface TextFieldProps extends 
FormElementTemplateProps<string> {
   placeholder: string;
 }
 
-export class TextFieldTemplate extends 
AbstractFormGroupInputTemplate<TextFieldProps> {
+export class TextFieldTemplate extends 
AbstractFormGroupTemplate<TextFieldProps> {
   constructor() {
     super(template(input), template(inputBindModelData), 
template(inputWriteModelData));
   }
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/UnsupportedTemplate.ts
 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/UnsupportedTemplate.ts
index 649f053be36..8c34ef6db43 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/UnsupportedTemplate.ts
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/UnsupportedTemplate.ts
@@ -18,9 +18,9 @@
  */
 
 import unsupported from 
"!!raw-loader!../../resources/templates/unsupported.template";
-import { FormElementTemplate, FormElementTemplateProps } from "./types";
+import { FormElementTemplate, FormElementTemplateProps } from 
"./AbstractFormGroupTemplate";
 import { CompiledTemplate, template } from "underscore";
-import { FormElement, FormInput } from "../../api";
+import { FormInput } from "../../api";
 import { getInputReference } from "../utils/Utils";
 
 export interface UnsupportedFieldProps extends FormElementTemplateProps<any> {
@@ -35,13 +35,12 @@ export class UnsupportedFieldTemplate implements 
FormElementTemplate<FormInput,
   }
 
   render(props: UnsupportedFieldProps): FormInput {
-    const data = {
-      props: props,
-    };
-
     return {
       ref: getInputReference(props),
-      html: this.unsupportedTemplate(data),
+      html: this.unsupportedTemplate({ props }),
+      globalFunctions: undefined,
+      setValueFromModelCode: undefined,
+      writeValueToModelCode: undefined,
     };
   }
 }
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/templates.ts
 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/templates.ts
index 84e22cd9e0c..ba171142db9 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/templates.ts
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/templates.ts
@@ -17,10 +17,10 @@
  * under the License.
  */
 
-import { CodeGenElement, FormElement } from "../../api";
+import { CodeGenElement } from "../../api";
 import { TextFieldTemplate } from "./TextFieldTemplate";
 import { NumFieldTemplate } from "./NumFieldTemplate";
-import { CodeGenTemplate, FormElementTemplate } from "./types";
+import { CodeGenTemplate } from "./AbstractFormGroupTemplate";
 import { BoolFieldTemplate } from "./BoolFieldTemplate";
 import { DateFieldTemplate } from "./DateFieldTemplate";
 import { RadioGroupFieldTemplate } from "./RadioGroupFieldTemplate";
@@ -29,12 +29,14 @@ import { SelectFieldTemplate } from "./SelectFieldTemplate";
 import { NestFieldTemplate } from "./NestFieldTemplate";
 import { AutoFormTemplate } from "./AutoFormTemplate";
 import { UnsupportedFieldTemplate } from "./UnsupportedTemplate";
+import { ListFieldTemplate } from "./ListFieldTemplate";
 
 export const FORM: string = "form";
 export const CHECKBOX: string = "checkbox";
 export const CHECKBOXGROUP: string = "checkboxGroup";
 export const DATE: string = "date";
 export const INPUT: string = "input";
+export const LIST: string = "listField";
 export const NESTED: string = "nestField";
 export const NUMBER: string = "number";
 export const RADIOGROUP: string = "radioGroup";
@@ -48,6 +50,7 @@ try {
   _templates.set(CHECKBOX, new BoolFieldTemplate());
   _templates.set(CHECKBOXGROUP, new CheckBoxGroupFieldTemplate());
   _templates.set(DATE, new DateFieldTemplate());
+  _templates.set(LIST, new ListFieldTemplate());
   _templates.set(NESTED, new NestFieldTemplate());
   _templates.set(NUMBER, new NumFieldTemplate());
   _templates.set(RADIOGROUP, new RadioGroupFieldTemplate());
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/types.ts 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/types.ts
deleted file mode 100644
index ce2b433e4e5..00000000000
--- 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/types.ts
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *  http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import formGroupTemplate from 
"!!raw-loader!../../resources/templates/formGroup.template";
-import { CodeFragment, CodeGenElement, FormElement, FormInput } from 
"../../api";
-import { CompiledTemplate, template } from "underscore";
-import { getInputReference } from "../utils/Utils";
-import { fieldNameToOptionalChain, flatFieldName } from "./utils";
-
-export interface CodeGenTemplate<Element extends CodeGenElement, Properties> {
-  render: (props: Properties) => Element;
-}
-
-export interface FormElementTemplateProps<Type> {
-  id: string;
-  name: string;
-  label: string;
-  disabled: boolean;
-  value: Type;
-}
-
-export interface FormElementTemplate<
-  Element extends FormElement<any>,
-  Properties extends FormElementTemplateProps<any>,
-> {
-  render: (props: Properties) => Element;
-}
-
-export const FORM_GROUP_TEMPLATE: CompiledTemplate = 
template(formGroupTemplate);
-
-export abstract class AbstractFormGroupInputTemplate<Properties extends 
FormElementTemplateProps<any>>
-  implements FormElementTemplate<FormInput, Properties>
-{
-  protected constructor(
-    readonly inputTemplate: CompiledTemplate,
-    readonly setValueFromModelTemplate: CompiledTemplate,
-    readonly writeValueToModelTemplate: CompiledTemplate
-  ) {}
-
-  render(props: Properties): FormInput {
-    const data = {
-      props: props,
-      input: this.inputTemplate({ props: props }),
-    };
-
-    return {
-      ref: getInputReference(props),
-      html: FORM_GROUP_TEMPLATE(data),
-      disabled: props.disabled,
-      setValueFromModelCode: this.buildSetValueFromModelCode(props),
-      writeValueToModelCode: this.writeValueToModelCode(props),
-    };
-  }
-
-  protected buildSetValueFromModelCode(props: Properties): CodeFragment {
-    const properties = {
-      ...props,
-      path: fieldNameToOptionalChain(props.name),
-      flatFieldName: flatFieldName(props.name),
-    };
-
-    return {
-      code: this.setValueFromModelTemplate(properties),
-    };
-  }
-
-  protected writeValueToModelCode(props: Properties): CodeFragment | undefined 
{
-    if (props.disabled || !this.writeValueToModelTemplate) {
-      return undefined;
-    }
-
-    const properties = {
-      ...props,
-      flatFieldName: flatFieldName(props.name),
-    };
-
-    return {
-      code: this.writeValueToModelTemplate(properties),
-    };
-  }
-}
diff --git 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/utils.ts 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/utils.ts
index 8941c87139e..de1d285e0a4 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/utils.ts
+++ 
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/utils.ts
@@ -18,17 +18,20 @@
  */
 
 export function fieldNameToOptionalChain(fieldName: string): string {
-  if (!fieldName) {
-    return "";
-  }
-
-  return fieldName.split(".").join("?.");
+  return !fieldName ? "" : fieldName.split(".").join("?.");
 }
 
 export function flatFieldName(fieldName: string): string {
-  if (!fieldName) {
+  return !fieldName ? "" : fieldName.split(".").join("__");
+}
+
+export function getItemValeuPath(name: string) {
+  if (name.endsWith("$")) {
     return "";
   }
-
-  return fieldName.split(".").join("__");
+  // nested object
+  if (name.includes("$.")) {
+    return `?.${name.split(".").pop()}`;
+  }
+  return "";
 }
diff --git 
a/packages/form-code-generator-bootstrap4-theme/tests/AutoField.test.tsx 
b/packages/form-code-generator-bootstrap4-theme/tests/AutoField.test.tsx
index b04e7d76ea5..08b48604440 100644
--- a/packages/form-code-generator-bootstrap4-theme/tests/AutoField.test.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/tests/AutoField.test.tsx
@@ -58,10 +58,7 @@ const schema = {
       type: "date",
     },
   },
-  friends: { type: Array },
-  "friends.$": Object,
-  "friends.$.name": { type: String },
-  "friends.$.age": { type: Number },
+  friends: { type: "foo" },
 };
 
 const doRenderField = (fieldName: string) => {
@@ -138,9 +135,9 @@ describe("<AutoField> tests", () => {
   it("<UnsupportedField> - rendering", () => {
     const { formElement } = doRenderField("friends");
 
-    expect(formElement.html).toContain("Unsupported field type: Array");
+    expect(formElement.html).toContain("Unsupported field type: ");
     expect(formElement.html).toContain(
-      `Cannot find form control for property <code>friends</code> with type 
<code>Array</code>.</p>`
+      `Cannot find form control for property <code>friends</code> with type 
<code></code>.</p>`
     );
   });
 });
diff --git 
a/packages/form-code-generator-bootstrap4-theme/tests/AutoForm.test.tsx 
b/packages/form-code-generator-bootstrap4-theme/tests/AutoForm.test.tsx
index bfd59cd405c..20f3b6ce1a3 100644
--- a/packages/form-code-generator-bootstrap4-theme/tests/AutoForm.test.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/tests/AutoForm.test.tsx
@@ -70,6 +70,8 @@ const schema = {
   "friends.$": Object,
   "friends.$.name": { type: String },
   "friends.$.age": { type: Number },
+  "friends.$.known": { type: Array },
+  "friends.$.known.$": String,
 };
 
 const props: AutoFormProps = {
diff --git 
a/packages/form-code-generator-bootstrap4-theme/tests/UnsupportedField.test.tsx 
b/packages/form-code-generator-bootstrap4-theme/tests/ListField.test.tsx
similarity index 55%
copy from 
packages/form-code-generator-bootstrap4-theme/tests/UnsupportedField.test.tsx
copy to packages/form-code-generator-bootstrap4-theme/tests/ListField.test.tsx
index 74e2bd7bd17..f83c48b7eac 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/tests/UnsupportedField.test.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/tests/ListField.test.tsx
@@ -17,36 +17,49 @@
  * under the License.
  */
 
-import * as React from "react";
 import { renderField } from "./_render";
-import { UnsupportedField } from "../src/uniforms";
+import { ListField } from "../src/uniforms";
 
 const schema = {
   friends: { type: Array },
   "friends.$": Object,
   "friends.$.name": { type: String },
   "friends.$.age": { type: Number },
+  "friends.$.country": { type: String, allowedValues: ["US", "Brazil"] },
+  "friends.$.married": { type: Boolean },
+  "friends.$.know": {
+    type: Array,
+    uniforms: {
+      checkboxes: true,
+    },
+  },
+  "friends.$.know.$": String,
+  "friends.$.areas": {
+    type: String,
+    allowedValues: ["Developer", "HR", "UX"],
+  },
+  "friends.$.birthday": { type: Date },
 };
 
-describe("<UnsupportedField> tests", () => {
-  it("<UnsupportedField> - rendering", () => {
+describe("<ListField> tests", () => {
+  it("<ListField> - rendering enabled", () => {
     const props = {
       id: "id",
-      label: "Friends?",
+      label: "Friends",
       name: "friends",
       disabled: false,
     };
 
-    const { container, formElement } = renderField(UnsupportedField, props, 
schema);
+    const { container, formElement } = renderField(ListField, props, schema);
 
     expect(container).toMatchSnapshot();
 
-    expect(formElement.html).toContain("Unsupported field type: Array");
-    expect(formElement.html).toContain(
-      `Cannot find form control for property <code>${props.name}</code> with 
type <code>Array</code>.</p>`
-    );
+    expect(formElement.html).toContain('<div role="list" id="friends">');
 
-    expect(formElement.setValueFromModelCode).toBeUndefined();
-    expect(formElement.writeValueToModelCode).toBeUndefined();
+    expect(formElement.ref).toHaveLength(7);
+
+    expect(formElement.globalFunctions).not.toBeUndefined();
+    expect(formElement.setValueFromModelCode).not.toBeUndefined();
+    expect(formElement.writeValueToModelCode).not.toBeUndefined();
   });
 });
diff --git 
a/packages/form-code-generator-bootstrap4-theme/tests/UnsupportedField.test.tsx 
b/packages/form-code-generator-bootstrap4-theme/tests/UnsupportedField.test.tsx
index 74e2bd7bd17..564222b90d2 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/tests/UnsupportedField.test.tsx
+++ 
b/packages/form-code-generator-bootstrap4-theme/tests/UnsupportedField.test.tsx
@@ -22,10 +22,7 @@ import { renderField } from "./_render";
 import { UnsupportedField } from "../src/uniforms";
 
 const schema = {
-  friends: { type: Array },
-  "friends.$": Object,
-  "friends.$.name": { type: String },
-  "friends.$.age": { type: Number },
+  friends: { type: "foo" },
 };
 
 describe("<UnsupportedField> tests", () => {
@@ -34,16 +31,15 @@ describe("<UnsupportedField> tests", () => {
       id: "id",
       label: "Friends?",
       name: "friends",
-      disabled: false,
     };
 
     const { container, formElement } = renderField(UnsupportedField, props, 
schema);
 
     expect(container).toMatchSnapshot();
 
-    expect(formElement.html).toContain("Unsupported field type: Array");
+    expect(formElement.html).toContain("Unsupported field type: ");
     expect(formElement.html).toContain(
-      `Cannot find form control for property <code>${props.name}</code> with 
type <code>Array</code>.</p>`
+      `Cannot find form control for property <code>${props.name}</code> with 
type <code></code>.</p>`
     );
 
     expect(formElement.setValueFromModelCode).toBeUndefined();
diff --git 
a/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/AutoForm.test.tsx.snap
 
b/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/AutoForm.test.tsx.snap
index 852267f6cfb..99396b3a3d2 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/AutoForm.test.tsx.snap
+++ 
b/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/AutoForm.test.tsx.snap
@@ -5,7 +5,7 @@ exports[`<AutoForm> tests <AutoForm> - Full rendering 1`] = `
   &lt;div&gt;
        &lt;fieldset&gt;
                &lt;legend&gt;Personal data&lt;/legend&gt;
-               &lt;div&gt;
+               &lt;div role=&quot;group&quot;&gt;
                        &lt;div class=&quot;form-group&quot;&gt;
                                &lt;label 
for=&quot;personalData.name&quot;&gt;Name&lt;/label&gt;
                                &lt;input type=&quot;text&quot; 
id=&quot;personalData.name&quot; name=&quot;personalData.name&quot; 
class=&quot;form-control&quot; value=&quot;&quot; /&gt;
@@ -18,7 +18,7 @@ exports[`<AutoForm> tests <AutoForm> - Full rendering 1`] = `
        &lt;/fieldset&gt;
        &lt;fieldset&gt;
                &lt;legend&gt;Address&lt;/legend&gt;
-               &lt;div&gt;
+               &lt;div role=&quot;group&quot;&gt;
                        &lt;div class=&quot;form-group&quot;&gt;
                                &lt;label 
for=&quot;address.street&quot;&gt;Street&lt;/label&gt;
                                &lt;input type=&quot;text&quot; 
id=&quot;address.street&quot; name=&quot;address.street&quot; 
class=&quot;form-control&quot; value=&quot;&quot; /&gt;
@@ -39,7 +39,7 @@ exports[`<AutoForm> tests <AutoForm> - Full rendering 1`] = `
        &lt;/fieldset&gt;
        &lt;fieldset&gt;
                &lt;legend&gt;Interview&lt;/legend&gt;
-               &lt;div&gt;
+               &lt;div role=&quot;group&quot;&gt;
                        &lt;div class=&quot;form-group&quot;&gt;
                                &lt;label 
for=&quot;interview.position&quot;&gt;Position&lt;/label&gt;
                                &lt;select class=&quot;form-control&quot; 
id=&quot;interview.position&quot; name=&quot;interview.position&quot;&gt;
@@ -116,16 +116,251 @@ exports[`<AutoForm> tests <AutoForm> - Full rendering 
1`] = `
                        &lt;/div&gt;
                &lt;/div&gt;
        &lt;/fieldset&gt;
-       &lt;div class=&quot;alert alert-warning&quot; role=&quot;alert&quot;&gt;
-               &lt;h4 class=&quot;alert-heading&quot;&gt;Unsupported field 
type: Array&lt;/h4&gt;
-               &lt;p&gt;Cannot find form control for property 
&lt;code&gt;friends&lt;/code&gt; with type 
&lt;code&gt;Array&lt;/code&gt;.&lt;/p&gt;
-               &lt;hr /&gt;
-               &lt;p class=&quot;mb-0&quot;&gt;
-                       Some complex property types, such as 
&lt;code&gt;Array&lt;/code&gt; aren&#39;t yet supported, however, you can still 
write your own component into the
-                       form and manually bind it.
-               &lt;/p&gt;
+       &lt;div class=&quot;form-group&quot;&gt;
+               &lt;div class=&quot;card mb-3&quot;&gt;
+                       &lt;div class=&quot;card-body&quot;&gt;
+                               &lt;div class=&quot;card-title&quot;&gt;
+                                       &lt;label 
class=&quot;col-form-label&quot;&gt;Friends&lt;/label&gt;
+                                       &lt;div
+                                               id=&quot;add-item-friends&quot;
+                                               name=&quot;Add Item&quot;
+                                               class=&quot;badge badge-pill 
float-right&quot;
+                                               role=&quot;button&quot;
+                                               tabindex=&quot;0&quot;
+                                               
onclick=&quot;onAddListItemFriends(&#39;friends&#39;)&quot;
+                                               
onkeydown=&quot;onAddListItemFriends(&#39;friends&#39;)&quot;
+                                       &gt;
+                                               &lt;i className=&quot;octicon 
octicon-plus&quot;&gt;&lt;span&gt;+&lt;/span&gt;&lt;/i&gt;
+                                       &lt;/div&gt;
+                               &lt;/div&gt;
+                               &lt;div role=&quot;list&quot; 
id=&quot;friends&quot;&gt;&lt;/div&gt;
+                       &lt;/div&gt;
+               &lt;/div&gt;
        &lt;/div&gt;
        &lt;script&gt;
+               // List Field Helper functions -- START --
+               function delListItem(name, minCount, itemIndex, onDelListItem) {
+                       const formData = getFormData();
+                       const value = accessObjectPath(formData, name) ?? [];
+                       if ((minCount ?? 0) &lt;= value.length) {
+                               
document.getElementById(\`item-\${name}.\${itemIndex}\`).remove();
+                       }
+                       // Re-organize list
+                       Array.from(document.getElementById(name).childNodes ?? 
[])
+                               .filter((node) =&gt; node.nodeType === 1)
+                               .forEach((element, index) =&gt; {
+                                       const delElement = 
element.querySelector(\`[id=&#39;remove-item-\${name}&#39;]\`);
+                                       delElement.onclick = function () {
+                                               onDelListItem(name, 
\`\${index}\`);
+                                       };
+                                       delElement.onkeydown = function () {
+                                               onDelListItem(name, 
\`\${index}\`);
+                                       };
+                                       const inputOrSelect = 
element.querySelector(&quot;input, select&quot;);
+                                       inputOrSelect.id = 
\`\${name}.\${index}\`;
+                                       inputOrSelect.name = 
\`\${name}.\${index}\`;
+                                       element.id = 
\`item-\${name}.\${index}\`;
+                               });
+               }
+               function addListItem(name, defaultValue, maxCount, 
childrenHtml, functionName) {
+                       const formData = getFormData();
+                       let value = accessObjectPath(formData, name) ?? [];
+                       const itemIndex = value.length;
+                       if (maxCount !== undefined &amp;&amp; maxCount &gt; 
value.length) {
+                               value = value.concat([defaultValue]);
+                       } else {
+                               value = value.concat([defaultValue]);
+                       }
+                       const listContainer = document.getElementById(name);
+                       const newItem = document.createElement(&quot;div&quot;);
+                       newItem.class = &quot;row&quot;;
+                       newItem.id = \`item-\${name}.\${itemIndex}\`;
+                       newItem.innerHTML = \`
+&lt;div class=&quot;col-1&quot;&gt;
+&lt;span id=&quot;remove-item-\${name}&quot; name=&quot;Remove Item&quot; 
class=&quot;badge badge-pill&quot; role=&quot;button&quot; 
tabindex=&quot;0&quot; 
onclick=&quot;onDelListItem\${functionName}(&#39;\${name}&#39;, 
\${itemIndex})&quot; 
onkeydown=&quot;onDelListItem\${functionName}(&#39;\${name}&#39;, 
\${itemIndex})&quot;&gt;
+  &lt;i className=&quot;octicon 
octicon-dash&quot;&gt;&lt;span&gt;-&lt;/span&gt;&lt;/i&gt;
+&lt;/span&gt;
+&lt;/div&gt;
+&lt;div class=&quot;col-11&quot;&gt;
+\${childrenHtml}
+&lt;/div&gt;
+\`;
+                       listContainer.appendChild(newItem);
+                       // Replace all &quot;$&quot; from \`input\` and 
\`select\` with the \`itemIndex\`
+                       const newItemElement = 
document.getElementById(newItem.id);
+                       
[...newItemElement.querySelectorAll(&#39;input[id*=&quot;$&quot;], 
select[id*=&quot;$&quot;]&#39;)].forEach((el) =&gt; {
+                               if (el.id.split(&quot;.&quot;).pop() !== 
&quot;$&quot;) {
+                                       el.id = 
\`\${name}.\${itemIndex}.\${el.id.split(&quot;.&quot;).pop()}\`;
+                                       el.name = 
\`\${name}.\${itemIndex}.\${el.id.split(&quot;.&quot;).pop()}\`;
+                               } else {
+                                       el.id = \`\${name}.\${itemIndex}\`;
+                                       el.name = \`\${name}.\${itemIndex}\`;
+                               }
+                       });
+                       // Get the root element of a nested list
+                       
[...newItemElement.querySelectorAll(&quot;div[role=&#39;list&#39;]&quot;)].forEach((nestedListContainer)
 =&gt; {
+                               if (!nestedListContainer) {
+                                       return;
+                               }
+                               // Replace &quot;$&quot; from the root element 
if necessary
+                               const nestedListPathWithIndex = 
splitLastOccurrence(nestedListContainer.id, 
&quot;$&quot;)?.[1]?.replace(&quot;$&quot;, \`\${itemIndex}\`);
+                               nestedListContainer.id = 
nestedListPathWithIndex ? \`\${name}.\${nestedListPathWithIndex}\` : name;
+                               // Replace &quot;$&quot; from all nested Add 
Item element
+                               
[...newItemElement.querySelectorAll(&quot;[id^=add-item]&quot;)].forEach((addItem)
 =&gt; {
+                                       const addItemPathWithIndex = 
splitLastOccurrence(addItem.id, &quot;$&quot;)?.[1]?.replace(&quot;$&quot;, 
\`\${itemIndex}\`);
+                                       addItem.id = addItemPathWithIndex ? 
\`add-item-\${name}.\${addItemPathWithIndex}\` : \`add-item-\${name}\`;
+                                       const addItemOnClick = 
addItem.getAttribute(&quot;onclick&quot;);
+                                       if (addItemOnClick) {
+                                               
addItem.setAttribute(&quot;onclick&quot;, getFunctionName(addItemOnClick, 
itemIndex));
+                                       }
+                                       const addItemOnKeydown = 
addItem.getAttribute(&quot;onkeydown&quot;);
+                                       if (addItemOnKeydown) {
+                                               
addItem.setAttribute(&quot;onkeydown&quot;, getFunctionName(addItemOnKeydown, 
itemIndex));
+                                       }
+                               });
+                               // Replace &quot;$&quot; from the Remove Item 
element
+                               
[...newItemElement.querySelectorAll(&quot;[id^=remove-item]&quot;)].forEach((removeItem)
 =&gt; {
+                                       const removeItemPathWithIndex = 
splitLastOccurrence(removeItem.id, &quot;$&quot;)?.[1]?.replace(&quot;$&quot;, 
\`\${itemIndex}\`);
+                                       removeItem.id = removeItemPathWithIndex 
? \`remove-item-\${name}.\${removeItemPathWithIndex}\` : 
\`remove-item-\${name}\`;
+                                       const removeItemOnClick = 
removeItem.getAttribute(&quot;onclick&quot;);
+                                       if (removeItemOnClick) {
+                                               
removeItem.setAttribute(&quot;onclick&quot;, getFunctionName(removeItemOnClick, 
itemIndex));
+                                       }
+                                       const removeItemOnKeydown = 
removeItem.getAttribute(&quot;onkeydown&quot;);
+                                       if (removeItemOnKeydown) {
+                                               
removeItem.setAttribute(&quot;onkeydown&quot;, 
getFunctionName(removeItemOnKeydown, itemIndex));
+                                       }
+                               });
+                       });
+               }
+               function getFunctionName(functionCall, itemIndex) {
+                       const [call, argument, end] = 
functionCall.split(&quot;&#39;&quot;);
+                       const [argumentName, argumentNameToReplace] = 
splitLastOccurrence(argument, &quot;$&quot;);
+                       return 
\`\${call}&#39;\${argumentName}\${argumentNameToReplace?.replace(&quot;$&quot;, 
\`\${itemIndex}\`) ?? &quot;&quot;}&#39;\${end}\`;
+               }
+               function splitLastOccurrence(str, char) {
+                       const lastIndex = str.lastIndexOf(char);
+                       if (lastIndex === -1) {
+                               return [str];
+                       }
+                       return [str.substring(0, lastIndex), 
str.substring(lastIndex)];
+               }
+               function getListValue(itemListElement) {
+                       function setValue(obj, path, value) {
+                               const keys = path.split(&quot;.&quot;);
+                               let current = obj;
+                               for (let i = 0; i &lt; keys.length; i++) {
+                                       const key = keys[i];
+                                       const isArray = !isNaN(keys[i + 1]);
+                                       if (i === keys.length - 1) {
+                                               current[key] = value;
+                                       } else {
+                                               if (!current[key]) {
+                                                       current[key] = isArray 
? [] : {};
+                                               }
+                                               if (isArray &amp;&amp; 
!Array.isArray(current[key])) {
+                                                       current[key] = [];
+                                               }
+                                               current = current[key];
+                                       }
+                               }
+                       }
+                       function traverse(element, result, path = &quot;&quot;) 
{
+                               element.querySelectorAll(&quot;input, 
select&quot;).forEach((input) =&gt; {
+                                       if (input.id) {
+                                               setValue(result, input.id, 
input.value || &quot;&quot;);
+                                       }
+                               });
+                       }
+                       const result = {};
+                       traverse(itemListElement, result);
+                       const [_, path] = 
itemListElement.id.split(&quot;item-&quot;);
+                       return accessObjectPath(result, path);
+               }
+               function accessObjectPath(obj, objPath) {
+                       return objPath.split(&quot;.&quot;)?.reduce((acc, 
pathPiece) =&gt; acc?.[pathPiece], obj);
+               }
+               // List Field Helper functions -- END --
+               function onDelListItemFriends(name, itemIndex) {
+                       const disabled = false;
+                       if (!disabled) {
+                               delListItem(name, undefined, itemIndex, 
onDelListItemFriends);
+                       }
+               }
+               function onAddListItemFriends(name, initialSetValues) {
+                       const disabled = false;
+                       if (!disabled || initialSetValues) {
+                               addListItem(
+                                       name,
+                                       undefined,
+                                       undefined,
+                                       \`&lt;fieldset&gt;
+    &lt;legend&gt;Friends&lt;/legend&gt;
+    &lt;div role=&quot;group&quot;&gt;
+        &lt;div class=&quot;form-group&quot;&gt;
+    &lt;label for=&quot;item-\${name}.$.name&quot;&gt;Name&lt;/label&gt;
+    &lt;input type=&quot;text&quot;
+    id=&quot;\${name}.$.name&quot;
+    name=&quot;\${name}.$.name&quot;
+    class=&quot;form-control&quot;
+    value=&quot;&quot;/&gt;
+&lt;/div&gt;
+        &lt;div class=&quot;form-group&quot;&gt;
+    &lt;label for=&quot;item-\${name}.$.age&quot;&gt;Age&lt;/label&gt;
+    &lt;input
+    type=&quot;number&quot;
+    class=&quot;form-control&quot;
+    id=&quot;\${name}.$.age&quot;
+    name=&quot;\${name}.$.age&quot;
+    step=&quot;0.01&quot;
+    value=&quot;&quot;/&gt;
+&lt;/div&gt;
+        &lt;div class=&quot;form-group&quot;&gt;
+    &lt;div class=&quot;card mb-3&quot;&gt;
+    &lt;div class=&quot;card-body&quot;&gt;
+        &lt;div class=&quot;card-title&quot;&gt;
+            &lt;label class=&quot;col-form-label&quot;&gt;Known&lt;/label&gt;
+            &lt;div id=&quot;add-item-\${name}.$.known&quot; name=&quot;Add 
Item&quot; class=&quot;badge badge-pill float-right&quot; 
role=&quot;button&quot; tabindex=&quot;0&quot; 
+                
onclick=&quot;onAddListItemFriends$Known(&#39;\${name}.$.known&#39;)&quot;
+                
onkeydown=&quot;onAddListItemFriends$Known(&#39;\${name}.$.known&#39;)&quot;&gt;
+                &lt;i className=&quot;octicon 
octicon-plus&quot;&gt;&lt;span&gt;+&lt;/span&gt;&lt;/i&gt;
+            &lt;/div&gt;
+        &lt;/div&gt;
+        &lt;div role=&quot;list&quot; 
id=&quot;\${name}.$.known&quot;&gt;&lt;/div&gt;
+    &lt;/div&gt;
+    &lt;/div&gt;
+&lt;/div&gt;
+    &lt;/div&gt;
+&lt;/fieldset&gt;\`,
+                                       &quot;Friends&quot;
+                               );
+                       }
+               }
+               function onDelListItemFriends$Known(name, itemIndex) {
+                       const disabled = false;
+                       if (!disabled) {
+                               delListItem(name, undefined, itemIndex, 
onDelListItemFriends$Known);
+                       }
+               }
+               function onAddListItemFriends$Known(name, initialSetValues) {
+                       const disabled = false;
+                       if (!disabled || initialSetValues) {
+                               addListItem(
+                                       name,
+                                       undefined,
+                                       undefined,
+                                       \`&lt;div 
class=&quot;form-group&quot;&gt;
+    &lt;label for=&quot;item-\${name}.$&quot;&gt;Known&lt;/label&gt;
+    &lt;input type=&quot;text&quot;
+    id=&quot;\${name}.$&quot;
+    name=&quot;\${name}.$&quot;
+    class=&quot;form-control&quot;
+    value=&quot;&quot;/&gt;
+&lt;/div&gt;\`,
+                                       &quot;Friends$Known&quot;
+                               );
+                       }
+               }
                /* Utility function that fills the form with the data received 
from the kogito runtime */
                function setFormData(data) {
                        if (!data) {
@@ -182,6 +417,19 @@ exports[`<AutoForm> tests <AutoForm> - Full rendering 1`] 
= `
                        
document.getElementById(&quot;interview.hiringDate&quot;).value = 
data?.interview?.hiringDate
                                ? new 
Date(data?.interview?.hiringDate).toISOString().slice(0, 16)
                                : &quot;&quot;;
+                       data?.friends?.forEach((value, index) =&gt; {
+                               // Add element;
+                               onAddListItemFriends(\`friends\`, true);
+                               const itemIndex__currentItem = 
\`friends.\${index}\`;
+                               
document.getElementById(\`\${itemIndex__currentItem}.name\`).value = 
value?.name ?? &quot;&quot;;
+                               
document.getElementById(\`\${itemIndex__currentItem}.age\`).value = value?.age 
?? &quot;&quot;;
+                               value?.known?.forEach((value, index) =&gt; {
+                                       // Add element;
+                                       
onAddListItemFriends$Known(\`\${itemIndex__currentItem}.known\`, true);
+                                       const nested__itemIndex__currentItem = 
\`\${itemIndex__currentItem}.known.\${index}\`;
+                                       
document.getElementById(\`\${nested__itemIndex__currentItem}\`).value = value 
?? &quot;&quot;;
+                               });
+                       });
                }
                /* Utility function to generate the expected form output as a 
json object */
                function getFormData() {
@@ -234,6 +482,9 @@ exports[`<AutoForm> tests <AutoForm> - Full rendering 1`] = 
`
                        formData.interview.rating = 
getRadioButtonGroupValue(&quot;interview.rating&quot;);
                        formData.interview.hire = 
document.getElementById(&quot;interview.hire&quot;).checked;
                        formData.interview.hiringDate = 
document.getElementById(&quot;interview.hiringDate&quot;).value;
+                       formData.friends = 
Array.from(document.getElementById(&quot;friends&quot;).childNodes).reduce((values,
 element) =&gt; {
+                               return [...values, getListValue(element)];
+                       }, []);
                        return formData;
                }
                /* Utility function to validate the form on the 
&#39;beforeSubmit&#39; Lifecycle Hook */
diff --git 
a/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/ListField.test.tsx.snap
 
b/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/ListField.test.tsx.snap
new file mode 100644
index 00000000000..4ff83ec15ab
--- /dev/null
+++ 
b/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/ListField.test.tsx.snap
@@ -0,0 +1,7 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<ListField> tests <ListField> - rendering enabled 1`] = `
+<div>
+  
{"ref":[{"id":"friends.$.name","binding":"friends.$.name"},{"id":"friends.$.age","binding":"friends.$.age"},{"id":"friends.$.country","binding":"friends.$.country"},{"id":"friends.$.married","binding":"friends.$.married"},{"id":"friends.$.know.$","binding":"friends.$.know.$"},{"id":"friends.$.areas","binding":"friends.$.areas"},{"id":"friends.$.birthday","binding":"friends.$.birthday"}],"html":"&lt;div
 class=\\"form-group\\"&gt;\\n    &lt;div class=\\"card mb-3\\"&gt;\\n    
&lt;div cla [...]
+</div>
+`;
diff --git 
a/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/NestField.test.tsx.snap
 
b/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/NestField.test.tsx.snap
index 3cc26039498..78cbf87194c 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/NestField.test.tsx.snap
+++ 
b/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/NestField.test.tsx.snap
@@ -2,12 +2,12 @@
 
 exports[`<NestField> tests <NestField> - rendering 1`] = `
 <div>
-  
{"ref":[{"id":"candidate.name","binding":"candidate.name"},{"id":"candidate.age","binding":"candidate.age"},{"id":"candidate.role","binding":"candidate.role"}],"html":"&lt;fieldset&gt;\\n
    &lt;legend&gt;Candidate&lt;/legend&gt;\\n    &lt;div&gt;\\n\\n        
&lt;div class=\\"form-group\\"&gt;\\n    &lt;label 
for=\\"candidate.name\\"&gt;Name&lt;/label&gt;\\n    &lt;input 
type=\\"text\\"\\n    id=\\"candidate.name\\"\\n    
name=\\"candidate.name\\"\\n    class=\\"form-control\\"\\n     [...]
+  
{"ref":[{"id":"candidate.name","binding":"candidate.name"},{"id":"candidate.age","binding":"candidate.age"},{"id":"candidate.role","binding":"candidate.role"}],"html":"&lt;fieldset&gt;\\n
    &lt;legend&gt;Candidate&lt;/legend&gt;\\n    &lt;div 
role=\\"group\\"&gt;\\n\\n        &lt;div class=\\"form-group\\"&gt;\\n    
&lt;label for=\\"candidate.name\\"&gt;Name&lt;/label&gt;\\n    &lt;input 
type=\\"text\\"\\n    id=\\"candidate.name\\"\\n    
name=\\"candidate.name\\"\\n    class=\\"form- [...]
 </div>
 `;
 
 exports[`<NestField> tests <NestField> - rendering disabled 1`] = `
 <div>
-  
{"ref":[{"id":"candidate.name","binding":"candidate.name"},{"id":"candidate.age","binding":"candidate.age"},{"id":"candidate.role","binding":"candidate.role"}],"html":"&lt;fieldset
 disabled&gt;\\n    &lt;legend&gt;Candidate&lt;/legend&gt;\\n    
&lt;div&gt;\\n\\n        &lt;div class=\\"form-group\\"&gt;\\n    &lt;label 
for=\\"candidate.name\\"&gt;Name&lt;/label&gt;\\n    &lt;input 
type=\\"text\\"\\n    id=\\"candidate.name\\"\\n    
name=\\"candidate.name\\"\\n    class=\\"form-control\ [...]
+  
{"ref":[{"id":"candidate.name","binding":"candidate.name"},{"id":"candidate.age","binding":"candidate.age"},{"id":"candidate.role","binding":"candidate.role"}],"html":"&lt;fieldset
 disabled&gt;\\n    &lt;legend&gt;Candidate&lt;/legend&gt;\\n    &lt;div 
role=\\"group\\"&gt;\\n\\n        &lt;div class=\\"form-group\\"&gt;\\n    
&lt;label for=\\"candidate.name\\"&gt;Name&lt;/label&gt;\\n    &lt;input 
type=\\"text\\"\\n    id=\\"candidate.name\\"\\n    
name=\\"candidate.name\\"\\n    class [...]
 </div>
 `;
diff --git 
a/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/UnsupportedField.test.tsx.snap
 
b/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/UnsupportedField.test.tsx.snap
index f3df9f01f0c..3380b605af4 100644
--- 
a/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/UnsupportedField.test.tsx.snap
+++ 
b/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/UnsupportedField.test.tsx.snap
@@ -2,6 +2,6 @@
 
 exports[`<UnsupportedField> tests <UnsupportedField> - rendering 1`] = `
 <div>
-  {"ref":{"id":"id","binding":"friends"},"html":"&lt;div class=\\"alert 
alert-warning\\" role=\\"alert\\"&gt;\\n  &lt;h4 
class=\\"alert-heading\\"&gt;Unsupported field type: Array&lt;/h4&gt;\\n  
&lt;p&gt;Cannot find form control for property &lt;code&gt;friends&lt;/code&gt; 
with type &lt;code&gt;Array&lt;/code&gt;.&lt;/p&gt;\\n  &lt;hr&gt;\\n  &lt;p 
class=\\"mb-0\\"&gt;Some complex property types, such as 
&lt;code&gt;Array&lt;/code&gt; aren't yet supported, however, you can still 
write y [...]
+  {"ref":{"id":"friends","binding":"friends"},"html":"&lt;div class=\\"alert 
alert-warning\\" role=\\"alert\\"&gt;\\n  &lt;h4 
class=\\"alert-heading\\"&gt;Unsupported field type: &lt;/h4&gt;\\n  
&lt;p&gt;Cannot find form control for property &lt;code&gt;friends&lt;/code&gt; 
with type &lt;code&gt;&lt;/code&gt;.&lt;/p&gt;\\n  &lt;hr&gt;\\n  &lt;p 
class=\\"mb-0\\"&gt;Some complex property types, such as 
&lt;code&gt;&lt;/code&gt; aren't yet supported, however, you can still write 
your own co [...]
 </div>
 `;


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to