diff --git a/ReadMe.md b/ReadMe.md
index 22611d8..63357ca 100644
--- a/ReadMe.md
+++ b/ReadMe.md
@@ -27,12 +27,13 @@ A **Pagination Table** & **Scroll List** component suite for [CRUD operation][1]
 5. [Form Field](https://idea2app.github.io/MobX-RESTful-table/functions/FormField-1.html)
 6. [Range Input](https://idea2app.github.io/MobX-RESTful-table/classes/RangeInput.html)
 7. [Badge Input](https://idea2app.github.io/MobX-RESTful-table/classes/BadgeInput.html)
-8. [REST Form](https://idea2app.github.io/MobX-RESTful-table/classes/RestForm.html)
-9. [Pager](https://idea2app.github.io/MobX-RESTful-table/functions/Pager-1.html)
-10. [REST Table](https://idea2app.github.io/MobX-RESTful-table/classes/RestTable.html)
-11. [Scroll Boundary](https://idea2app.github.io/MobX-RESTful-table/functions/ScrollBoundary-1.html)
-12. [Scroll List](https://idea2app.github.io/MobX-RESTful-table/classes/ScrollList.html)
-13. [Searchable Input](https://idea2app.github.io/MobX-RESTful-table/classes/SearchableInput.html)
+8. [Array Field](https://idea2app.github.io/MobX-RESTful-table/classes/ArrayField.html)
+9. [REST Form](https://idea2app.github.io/MobX-RESTful-table/classes/RestForm.html)
+10. [Pager](https://idea2app.github.io/MobX-RESTful-table/functions/Pager-1.html)
+11. [REST Table](https://idea2app.github.io/MobX-RESTful-table/classes/RestTable.html)
+12. [Scroll Boundary](https://idea2app.github.io/MobX-RESTful-table/functions/ScrollBoundary-1.html)
+13. [Scroll List](https://idea2app.github.io/MobX-RESTful-table/classes/ScrollList.html)
+14. [Searchable Input](https://idea2app.github.io/MobX-RESTful-table/classes/SearchableInput.html)
 
 ## Installation
 
diff --git a/package.json b/package.json
index add3dbf..27930e8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "mobx-restful-table",
-  "version": "2.3.0",
+  "version": "2.4.0",
   "license": "LGPL-3.0",
   "author": "shiy2008@gmail.com",
   "description": "A Pagination Table & Scroll List component suite for CRUD operation, which is based on MobX RESTful & React.",
@@ -34,6 +34,7 @@
     "mobx-react-helper": "^0.4.1",
     "mobx-restful": "^2.1.0",
     "react-bootstrap": "^2.10.10",
+    "react-bootstrap-editor": "^2.1.1",
     "regenerator-runtime": "^0.14.1",
     "web-utility": "^4.4.3"
   },
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 02cacbb..30ad21a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -38,6 +38,9 @@ importers:
       react-bootstrap:
         specifier: ^2.10.10
         version: 2.10.10(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+      react-bootstrap-editor:
+        specifier: ^2.1.1
+        version: 2.1.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3)
       regenerator-runtime:
         specifier: ^0.14.1
         version: 0.14.1
@@ -180,6 +183,9 @@ packages:
     resolution: {integrity: sha512-iA7+tyVqfrATAIsIRWQG+a7ZLLD0VaOCKV2Wd/v4mqIU3J9c4jx9p7S0nw1XH3gJCKNBOOwACOPYYSUu9pgT+w==}
     engines: {node: '>=12.0.0'}
 
+  '@mixmark-io/domino@2.2.0':
+    resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==}
+
   '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3':
     resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==}
     cpu: [arm64]
@@ -770,6 +776,9 @@ packages:
   '@types/react@19.1.5':
     resolution: {integrity: sha512-piErsCVVbpMMT2r7wbawdZsq4xMvIAhQuac2gedQHysu1TZYEigE6pnFfgZT+/jQnrRuF5r+SHzuehFjfRjr4g==}
 
+  '@types/turndown@5.0.5':
+    resolution: {integrity: sha512-TL2IgGgc7B5j78rIccBtlYAnkuv8nUQqhQc+DSYV5j9Be9XOcm/SKOVRuA47xAVI3680Tk9B1d8flK2GWT2+4w==}
+
   '@types/unist@3.0.3':
     resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
 
@@ -816,6 +825,9 @@ packages:
     resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
     engines: {node: '>=8'}
 
+  browser-fs-access@0.37.0:
+    resolution: {integrity: sha512-MKpvZrKtv6pBJ2ACd+VwfS9XauBKTMVZg2UBibypuK1gfiXM7euZjbdKmvRsyxeQRhfzNVQrzCSVGXs19/LP8Q==}
+
   browserslist@4.24.5:
     resolution: {integrity: sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==}
     engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
@@ -918,8 +930,11 @@ packages:
   editorjs-html@4.0.5:
     resolution: {integrity: sha512-ImQYxB3fNCJcd+nJ+Vbne/6PxidO1cYByNpu9nBDStVabfjVrMW65BuR+IEZfOii8VKYH+CW/lYDb2GDlzZtDg==}
 
-  electron-to-chromium@1.5.156:
-    resolution: {integrity: sha512-QeOqv11TSASsY/3Ft3LUyDqEiEOph5/85srEPFUo9wuGFNTb0/z5fGE/+ZzTrYvSTGoXNkdQLTw3MKRmgyOyGA==}
+  edkit@1.2.7:
+    resolution: {integrity: sha512-dCOBN9MMbCaCdSqhnZTSHPe7lu53TQttttjVBxLE/TehsQasuxmqW3ckimVODFaJVci1A6w429j9bebpiU3zKg==}
+
+  electron-to-chromium@1.5.157:
+    resolution: {integrity: sha512-/0ybgsQd1muo8QlnuTpKwtl0oX5YMlUGbm8xyqgDU00motRkKFFbUJySAQBWcY79rVqNLWIWa87BGVGClwAB2w==}
 
   element-internals-polyfill@1.3.13:
     resolution: {integrity: sha512-viZ7wJsvh6eFwGQX512zEaccK/c6RRFSerJsdkfe3DW/ZtruvNeOR33fpPZgfXxvqRdU2lK33KM4S6GqaTgVKQ==}
@@ -1159,6 +1174,11 @@ packages:
     resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
     hasBin: true
 
+  marked@15.0.12:
+    resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==}
+    engines: {node: '>= 18'}
+    hasBin: true
+
   mdurl@2.0.0:
     resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
 
@@ -1327,6 +1347,12 @@ packages:
     resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
     engines: {node: '>=6'}
 
+  react-bootstrap-editor@2.1.1:
+    resolution: {integrity: sha512-vnAy1MSn4mAZp418cz3R7IiUWXGGwNfmWQYlPUa2MWGm6hTTxJVeKTp2jhrH3n8sbbtg2MN4/co44OP+sZn1pQ==}
+    peerDependencies:
+      react: '>=16'
+      react-dom: '>=16'
+
   react-bootstrap@2.10.10:
     resolution: {integrity: sha512-gMckKUqn8aK/vCnfwoBpBVFUGT9SVQxwsYrp9yDHt0arXMamxALerliKBxr1TPbntirK/HGrUAHYbAeQTa9GHQ==}
     peerDependencies:
@@ -1460,6 +1486,12 @@ packages:
   tslib@2.8.1:
     resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
 
+  turndown-plugin-gfm@1.0.2:
+    resolution: {integrity: sha512-vwz9tfvF7XN/jE0dGoBei3FXWuvll78ohzCZQuOb+ZjWrs3a0XhQVomJEb2Qh4VHTPNRO4GPZh0V7VRbiWwkRg==}
+
+  turndown@7.2.0:
+    resolution: {integrity: sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A==}
+
   type-fest@0.20.2:
     resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
     engines: {node: '>=10'}
@@ -1613,6 +1645,8 @@ snapshots:
       '@lezer/lr': 1.4.2
       json5: 2.2.3
 
+  '@mixmark-io/domino@2.2.0': {}
+
   '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3':
     optional: true
 
@@ -2467,6 +2501,8 @@ snapshots:
     dependencies:
       csstype: 3.1.3
 
+  '@types/turndown@5.0.5': {}
+
   '@types/unist@3.0.3': {}
 
   '@types/warning@3.0.3': {}
@@ -2503,10 +2539,12 @@ snapshots:
     dependencies:
       fill-range: 7.1.1
 
+  browser-fs-access@0.37.0: {}
+
   browserslist@4.24.5:
     dependencies:
       caniuse-lite: 1.0.30001718
-      electron-to-chromium: 1.5.156
+      electron-to-chromium: 1.5.157
       node-releases: 2.0.19
       update-browserslist-db: 1.1.3(browserslist@4.24.5)
 
@@ -2583,7 +2621,20 @@ snapshots:
 
   editorjs-html@4.0.5: {}
 
-  electron-to-chromium@1.5.156: {}
+  edkit@1.2.7(typescript@5.8.3):
+    dependencies:
+      '@swc/helpers': 0.5.17
+      '@types/turndown': 5.0.5
+      browser-fs-access: 0.37.0
+      marked: 15.0.12
+      regenerator-runtime: 0.14.1
+      turndown: 7.2.0
+      turndown-plugin-gfm: 1.0.2
+      web-utility: 4.4.3(typescript@5.8.3)
+    transitivePeerDependencies:
+      - typescript
+
+  electron-to-chromium@1.5.157: {}
 
   element-internals-polyfill@1.3.13: {}
 
@@ -2829,6 +2880,8 @@ snapshots:
       punycode.js: 2.3.1
       uc.micro: 2.1.0
 
+  marked@15.0.12: {}
+
   mdurl@2.0.0: {}
 
   micromatch@4.0.8:
@@ -3015,6 +3068,20 @@ snapshots:
 
   punycode.js@2.3.1: {}
 
+  react-bootstrap-editor@2.1.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3):
+    dependencies:
+      '@swc/helpers': 0.5.17
+      edkit: 1.2.7(typescript@5.8.3)
+      mobx: 6.13.7
+      mobx-react: 9.2.0(mobx@6.13.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+      mobx-react-helper: 0.4.1(mobx@6.13.7)(react@19.1.0)(typescript@5.8.3)
+      react: 19.1.0
+      react-dom: 19.1.0(react@19.1.0)
+      web-utility: 4.4.3(typescript@5.8.3)
+    transitivePeerDependencies:
+      - react-native
+      - typescript
+
   react-bootstrap@2.10.10(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
     dependencies:
       '@babel/runtime': 7.27.1
@@ -3157,6 +3224,12 @@ snapshots:
 
   tslib@2.8.1: {}
 
+  turndown-plugin-gfm@1.0.2: {}
+
+  turndown@7.2.0:
+    dependencies:
+      '@mixmark-io/domino': 2.2.0
+
   type-fest@0.20.2: {}
 
   typedoc-plugin-mdn-links@5.0.2(typedoc@0.28.4(typescript@5.8.3)):
diff --git a/preview/content.tsx b/preview/content.tsx
index d60f3d0..6c5a6f7 100644
--- a/preview/content.tsx
+++ b/preview/content.tsx
@@ -1,9 +1,10 @@
 import { text2color } from 'idea-react';
 import { GitRepository } from 'mobx-github';
 import { FC } from 'react';
-import { Badge } from 'react-bootstrap';
+import { Badge, Form, InputGroup } from 'react-bootstrap';
 
 import {
+  ArrayField,
   BadgeInput,
   Column,
   FileModel,
@@ -20,6 +21,11 @@ import {
 import { i18n, repositoryStore, topicStore } from './model';
 import { CodeExample, Section } from './utility';
 
+interface Price {
+  currency: 'USD' | 'CNY';
+  amount: number;
+}
+
 class MyFileModel extends FileModel {}
 
 const columns: Column<GitRepository>[] = [
@@ -55,6 +61,16 @@ const columns: Column<GitRepository>[] = [
     ),
   },
   { key: 'stargazers_count', type: 'number', renderHead: 'Star Count' },
+  {
+    key: 'description',
+    contentEditable: true,
+    renderHead: 'Description',
+    renderBody: ({ description }) => (
+      <p className="m-0 text-truncate" style={{ maxWidth: '10rem' }} title={description}>
+        {description}
+      </p>
+    ),
+  },
 ];
 
 export const Content: FC = () => (
@@ -117,6 +133,31 @@ export const Content: FC = () => (
       </CodeExample>
     </Section>
 
+    <Section title="Array Field">
+      <CodeExample>
+        <ArrayField
+          name="prices"
+          renderItem={({ currency, amount }: Price) => (
+            <InputGroup>
+              <Form.Select name="currency" defaultValue={currency}>
+                <option value="USD">USD $</option>
+                <option value="CNY">CNY ¥</option>
+              </Form.Select>
+              <Form.Control
+                placeholder="Amount"
+                type="number"
+                name="amount"
+                required
+                min={0}
+                defaultValue={amount}
+              />
+            </InputGroup>
+          )}
+          onChange={console.log}
+        />
+      </CodeExample>
+    </Section>
+
     <Section title="Image Preview">
       <CodeExample>
         <ImagePreview src="https://github.com/idea2app.png" />
diff --git a/source/ArrayField.tsx b/source/ArrayField.tsx
new file mode 100644
index 0000000..c5a391b
--- /dev/null
+++ b/source/ArrayField.tsx
@@ -0,0 +1,81 @@
+import { toJS } from 'mobx';
+import { observer } from 'mobx-react';
+import { FormComponent, FormComponentProps } from 'mobx-react-helper';
+import { DataObject } from 'mobx-restful';
+import { ChangeEvent, HTMLAttributes, ReactNode } from 'react';
+import { Button, ButtonGroup } from 'react-bootstrap';
+import { formToJSON, isEmpty } from 'web-utility';
+
+export type ArrayFieldProps<T extends DataObject = DataObject> = Pick<
+  HTMLAttributes<HTMLFieldSetElement>,
+  'className' | 'style'
+> &
+  FormComponentProps<T[]> & {
+    renderItem: (item: T, index: number) => ReactNode;
+  };
+
+@observer
+export class ArrayField<T extends DataObject = DataObject> extends FormComponent<
+  ArrayFieldProps<T>
+> {
+  componentDidMount() {
+    super.componentDidMount();
+
+    if (isEmpty(this.value)) this.add();
+  }
+
+  add = () => (this.innerValue = [...(this.innerValue || []), {} as T]);
+
+  remove = (index: number) => (this.innerValue = this.innerValue?.filter((_, i) => i !== index));
+
+  handleChange =
+    (index: number) =>
+    ({ currentTarget }: ChangeEvent<EventTarget>) => {
+      const item = formToJSON<T>(currentTarget as HTMLFieldSetElement),
+        { innerValue } = this;
+
+      const list = [...innerValue!.slice(0, index), item, ...innerValue!.slice(index + 1)].map(
+        item => toJS(item),
+      );
+      this.props.onChange?.(list);
+    };
+
+  handleUpdate =
+    (index: number) =>
+    ({ currentTarget }: ChangeEvent<EventTarget>) =>
+      (this.innerValue![index] = formToJSON<T>(currentTarget as HTMLFieldSetElement));
+
+  render() {
+    const { className = '', style, name, renderItem } = this.props;
+
+    return (
+      <>
+        {this.value?.map((item, index, { length }) => (
+          <fieldset
+            key={JSON.stringify(item)}
+            className={`d-flex align-items-center my-2 gap-2 ${className}`}
+            {...{ style, name }}
+            onChange={this.handleChange(index)}
+            onBlur={this.handleUpdate(index)}
+          >
+            <div className="flex-fill">{renderItem(item, index)}</div>
+            <ButtonGroup>
+              <Button type="button" size="sm" variant="warning" onClick={this.add}>
+                +
+              </Button>
+              <Button
+                type="button"
+                size="sm"
+                variant="danger"
+                disabled={length < 2}
+                onClick={() => this.remove(index)}
+              >
+                -
+              </Button>
+            </ButtonGroup>
+          </fieldset>
+        ))}
+      </>
+    );
+  }
+}
diff --git a/source/RestForm.tsx b/source/RestForm.tsx
index 3f6b783..67a255f 100644
--- a/source/RestForm.tsx
+++ b/source/RestForm.tsx
@@ -3,8 +3,9 @@ import { TranslationModel } from 'mobx-i18n';
 import { observer } from 'mobx-react';
 import { ObservedComponent } from 'mobx-react-helper';
 import { DataObject, Filter, IDType, ListModel } from 'mobx-restful';
-import { FormEvent, Fragment, InputHTMLAttributes, ReactNode, TextareaHTMLAttributes } from 'react';
+import { FormEvent, Fragment, InputHTMLAttributes, ReactNode } from 'react';
 import { Button, Form, FormGroupProps, FormProps } from 'react-bootstrap';
+import { Editor, EditorProps } from 'react-bootstrap-editor';
 import { formatDate, formToJSON, isEmpty } from 'web-utility';
 
 import { FilePreview } from './FilePreview';
@@ -27,7 +28,8 @@ export interface Field<T extends DataObject>
       | 'accept'
       | 'placeholder'
     >,
-    Pick<FormFieldProps, 'options' | 'rows'> {
+    Pick<FormFieldProps, 'options' | 'rows' | 'contentEditable'>,
+    Pick<EditorProps, 'tools'> {
   key?: keyof T;
   renderLabel?: ReactNode | ((data: keyof T) => ReactNode);
   renderInput?: (data: T, meta: Field<T>) => ReactNode;
@@ -112,8 +114,10 @@ export class RestForm<
           ? this.renderFile(meta)
           : (meta.type === 'radio' || meta.type === 'checkbox') && meta.options
             ? this.renderCheckGroup(meta)
-            : meta.key &&
-              this.renderField(meta, meta.rows && !meta.options ? { as: 'textarea' } : {})),
+            : meta.contentEditable
+              ? this.renderHTMLEditor(meta)
+              : meta.key &&
+                this.renderField(meta, meta.rows && !meta.options ? { as: 'textarea' } : {})),
     }));
   }
 
@@ -151,11 +155,11 @@ export class RestForm<
     };
   renderCheckGroup =
     ({ key, type, options, renderLabel }: Field<D>) =>
-    (data: D) =>
-      this.fieldReady && (
-        <RestForm.FieldBox name={key} renderLabel={renderLabel}>
-          <div>
-            {options.map(({ value, text = value }) => (
+    (data: D) => (
+      <RestForm.FieldBox name={key} renderLabel={renderLabel}>
+        <div>
+          {this.fieldReady &&
+            options.map(({ value, text = value }) => (
               <Form.Check
                 key={value}
                 id={[key, value] + ''}
@@ -167,9 +171,17 @@ export class RestForm<
                 defaultChecked={data[key]?.includes(value)}
               />
             ))}
-          </div>
-        </RestForm.FieldBox>
-      );
+        </div>
+      </RestForm.FieldBox>
+    );
+
+  renderHTMLEditor =
+    ({ key, renderLabel }: Field<D>) =>
+    (data: D) => (
+      <RestForm.FieldBox name={key} renderLabel={renderLabel}>
+        {this.fieldReady && <Editor name={key?.toString()} defaultValue={data[key!] as string} />}
+      </RestForm.FieldBox>
+    );
 
   renderField = (
     { key, type, step, renderLabel, renderInput, ...meta }: Field<D>,
@@ -205,7 +217,7 @@ export class RestForm<
         onReset={() => store.clearCurrent()}
       >
         {fields.map(({ renderInput, ...meta }) => (
-          <Fragment key={meta.key.toString()}>{renderInput?.(currentOne, meta)}</Fragment>
+          <Fragment key={meta.key?.toString()}>{renderInput?.(currentOne, meta)}</Fragment>
         ))}
         {!readOnly && (
           <footer className="d-flex gap-3">
diff --git a/source/index.ts b/source/index.ts
index 1f7fa43..2ff97cd 100644
--- a/source/index.ts
+++ b/source/index.ts
@@ -5,6 +5,7 @@ export * from './FileUploader';
 export * from './FormField';
 export * from './RangeInput';
 export * from './BadgeInput';
+export * from './ArrayField';
 export * from './RestForm';
 export * from './Pager';
 export * from './RestTable';
