diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/static/js/index.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/static/js/index.js
index bbf7a1887..e2c43c627 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/static/js/index.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/static/js/index.js
@@ -7,6 +7,9 @@
 //
 //////////////////////////////////////////////////////////////
 
+import IndexSchema, { getColumnSchema } from './index.ui';
+import { getNodeAjaxOptions, getNodeListByName } from 'pgbrowser/node_ajax';
+
 define('pgadmin.node.index', [
   'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
   'backbone', 'sources/pgadmin', 'pgadmin.browser', 'pgadmin.alertifyjs',
@@ -32,191 +35,6 @@ define('pgadmin.node.index', [
       });
   }
 
-  // Node-Ajax-Cell with Deps
-  var NodeAjaxOptionsDepsCell = Backgrid.Extension.NodeAjaxOptionsCell.extend({
-    initialize: function() {
-      Backgrid.Extension.NodeAjaxOptionsCell.prototype.initialize.apply(this, arguments);
-      Backgrid.Extension.DependentCell.prototype.initialize.apply(this, arguments);
-    },
-    dependentChanged: function () {
-      var model = this.model,
-        column = this.column,
-        editable = this.column.get('editable'),
-        input = this.$el.find('select').first();
-
-      var is_editable = _.isFunction(editable) ? !!editable.apply(column, [model]) : !!editable;
-      if (is_editable) {
-        this.$el.addClass('editable');
-        input.prop('disabled', false);
-      } else {
-        this.$el.removeClass('editable');
-        input.prop('disabled', true);
-      }
-
-      this.delegateEvents();
-      return this;
-    },
-    remove: Backgrid.Extension.DependentCell.prototype.remove,
-  });
-
-
-  // Model to create column collection control
-  var ColumnModel = pgAdmin.Browser.Node.Model.extend({
-    defaults: {
-      colname: undefined,
-      collspcname: undefined,
-      op_class: undefined,
-      sort_order: false,
-      nulls: false,
-      is_sort_nulls_applicable: true,
-    },
-    schema: [
-      {
-        id: 'colname', label: gettext('Column'), cell: 'node-list-by-name',
-        type: 'text', disabled: 'inSchema', readonly: 'isEditMode', editable: function(m) {
-          // Header cell then skip
-          if (m instanceof Backbone.Collection) {
-            return false;
-          }
-          return !(m.inSchemaWithModelCheck.apply(this, arguments));
-        },
-        control: 'node-list-by-name', node: 'column',
-      },{
-        id: 'collspcname', label: gettext('Collation'),
-        cell: NodeAjaxOptionsDepsCell,
-        type: 'text', disabled: 'inSchema', readonly: 'isEditMode', editable: function(m) {
-          // Header cell then skip
-          if (m instanceof Backbone.Collection) {
-            return false;
-          }
-          return !(m.inSchemaWithModelCheck.apply(this, arguments));
-        },
-        control: 'node-ajax-options', url: 'get_collations', node: 'index',
-        url_jump_after_node: 'schema',
-      },{
-        id: 'op_class', label: gettext('Operator class'),
-        cell: NodeAjaxOptionsDepsCell, tags: true,
-        type: 'text', disabled: 'checkAccessMethod',
-        editable: function(m) {
-          // Header cell then skip
-          if (m instanceof Backbone.Collection || m.inSchemaWithModelCheck.apply(this, arguments)) {
-            return false;
-          }
-          return !(m.checkAccessMethod.apply(this, arguments));
-        },
-        control: 'node-ajax-options', url: 'get_op_class', node: 'index',
-        url_jump_after_node: 'schema',
-        deps: ['amname'], transform: function(data, control) {
-          /* We need to extract data from collection according
-           * to access method selected by user if not selected
-           * send btree related op_class options
-           */
-          var amname = control.model.top.get('amname'),
-            options = data['btree'];
-
-          if(_.isUndefined(amname))
-            return options;
-
-          _.each(data, function(v, k) {
-            if(amname === k) {
-              options = v;
-            }
-          });
-          return options;
-        },
-      },{
-        id: 'sort_order', label: gettext('Sort order'),
-        cell: Backgrid.Extension.TableChildSwitchCell, type: 'switch',
-        editable: function(m) {
-          // Header cell then skip
-          if (m instanceof Backbone.Collection) {
-            return false;
-          } else if (m.inSchemaWithModelCheck.apply(this, arguments)) {
-            return false;
-          } else if (m.top.get('amname') === 'btree') {
-            m.set('is_sort_nulls_applicable', true);
-            return true;
-          } else {
-            m.set('is_sort_nulls_applicable', false);
-            return false;
-          }
-        },
-        deps: ['amname'],
-        options: {
-          'onText': 'DESC', 'offText': 'ASC',
-        },
-      },{
-        id: 'nulls', label: gettext('NULLs'),
-        cell: Backgrid.Extension.TableChildSwitchCell, type: 'switch',
-        editable: function(m) {
-          // Header cell then skip
-          if (m instanceof Backbone.Collection) {
-            return false;
-          } else if (m.inSchemaWithModelCheck.apply(this, arguments)) {
-            return false;
-          } else if (m.top.get('amname') === 'btree') {
-            m.set('is_sort_nulls_applicable', true);
-            return true;
-          } else {
-            m.set('is_sort_nulls_applicable', false);
-            return false;
-          }
-        },
-        deps: ['amname', 'sort_order'],
-        options: {
-          'onText': 'FIRST', 'offText': 'LAST',
-        },
-      },
-    ],
-    validate: function() {
-      this.errorModel.clear();
-
-      if (_.isUndefined(this.get('colname'))
-        || String(this.get('colname')).replace(/^\s+|\s+$/g, '') == '') {
-        var msg = gettext('Column Name cannot be empty.');
-        this.errorModel.set('colname', msg);
-        return msg;
-      }
-    },
-    // We will check if we are under schema node
-    inSchema: function() {
-      if(this.node_info &&  'catalog' in this.node_info) {
-        return true;
-      }
-      return false;
-    },
-    isEditMode: function(m) {
-      return !m.top.isNew();
-    },
-    // We will check if we are under schema node & in 'create' mode
-    inSchemaWithModelCheck: function(m) {
-      if(m.top.node_info &&  'schema' in m.top.node_info) {
-        // We will disable control if it's in 'edit' mode
-        return !m.top.isNew();
-      }
-      return true;
-    },
-    // We will check if we are under schema node and added condition
-    checkAccessMethod: function(m) {
-      //Access method is empty or btree then do not disable field
-      var parent_model = m.top;
-      if(_.isUndefined(parent_model.get('amname')) ||
-        _.isNull(parent_model.get('amname')) ||
-          String(parent_model.get('amname')).replace(/^\s+|\s+$/g, '') == '' ||
-          parent_model.get('amname') === 'btree') {
-        // We need to set nulls to true if sort_order is set to desc
-        // nulls first is default for desc
-        if(m.get('sort_order') == true && m.previous('sort_order') ==  false) {
-          setTimeout(function() { m.set('nulls', true); }, 10);
-        }
-      }
-      else {
-        m.set('is_sort_nulls_applicable', false);
-      }
-      return false;
-    },
-  });
-
   if (!pgBrowser.Nodes['index']) {
     pgAdmin.Browser.Nodes['index'] = pgBrowser.Node.extend({
       parent_type: ['table', 'view', 'mview', 'partition'],
@@ -291,253 +109,11 @@ define('pgadmin.node.index', [
         },{
           id: 'oid', label: gettext('OID'), cell: 'string',
           type: 'int', readonly: true, mode: ['properties'],
-        },{
-          id: 'spcname', label: gettext('Tablespace'), cell: 'string',
-          control: 'node-list-by-name', node: 'tablespace',
-          select2: {'allowClear': true},
-          type: 'text', mode: ['properties', 'create', 'edit'],
-          disabled: 'inSchema', filter: function(d) {
-            // If tablespace name is not "pg_global" then we need to exclude them
-            if(d && d.label.match(/pg_global/))
-            {
-              return false;
-            }
-            return true;
-          },
-        },{
-          id: 'amname', label: gettext('Access Method'), cell: 'string',
-          type: 'text', mode: ['properties', 'create', 'edit'],
-          disabled: 'inSchema', readonly: 'isEditMode', url: 'get_access_methods',
-          url_jump_after_node: 'schema',
-          group: gettext('Definition'), select2: {'allowClear': true},
-          control: Backform.NodeAjaxOptionsControl.extend({
-            // When access method changes we need to clear columns collection
-            onChange: function() {
-              Backform.NodeAjaxOptionsControl.prototype.onChange.apply(this, arguments);
-              var self = this,
-                // current access method
-                current_am = self.model.get('amname'),
-                // previous access method
-                previous_am = self.model.previous('amname');
-              if (current_am != previous_am && self.model.get('columns').length !== 0) {
-                var msg = gettext('Changing access method will clear columns collection');
-                Alertify.confirm(msg, function () {
-                  // User clicks Ok, lets clear collection
-                  var column_collection = self.model.get('columns'),
-                    col_length = column_collection.length;
-                  for (var i=(col_length-1);i>=0;i--) {
-                    column_collection.remove(column_collection.models[i]);
-                  }
-                }, function() {
-                  // User clicks Cancel set previous value again in combo box
-                  setTimeout(function(){
-                    self.model.set('amname', previous_am);
-                  }, 10);
-                });
-              }
-            },
-          }),
-        },{
-          id: 'columns_csv', label: gettext('Columns'), cell: 'string',
-          type: 'text', disabled: 'inSchema', mode: ['properties'],
-          group: gettext('Definition'),
-        },{
-          id: 'include', label: gettext('Include columns'),
-          type: 'array', group: gettext('Definition'),
-          editable: false,
-          canDelete: true, canAdd: true, mode: ['properties'],
-          disabled: 'inSchema', readonly: 'isEditMode',
-          visible: function(m) {
-            if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
-              && !_.isUndefined(m.node_info.server.version) &&
-              m.node_info.server.version >= 110000)
-              return true;
-
-            return false;
-          },
-          control: Backform.MultiSelectAjaxControl.extend({
-            defaults: _.extend(
-              {},
-              Backform.NodeListByNameControl.prototype.defaults,
-              {
-                select2: {
-                  allowClear: false,
-                  width: 'style',
-                  multiple: true,
-                  placeholder: gettext('Select the column(s)'),
-                },
-              }
-            ),
-          }),
-          transform : function(data){
-            var res = [];
-            if (data && _.isArray(data)) {
-              _.each(data, function(d) {
-                res.push({label: d.label, value: d.label, image:'icon-column'});
-              });
-            }
-            return res;
-          },
-          node:'column',
-        },{
-          id: 'fillfactor', label: gettext('Fill factor'), cell: 'string',
-          type: 'int', disabled: 'inSchema', mode: ['create', 'edit', 'properties'],
-          min: 10, max:100, group: gettext('Definition'),
-        },{
-          id: 'indisunique', label: gettext('Unique?'), cell: 'string',
-          type: 'switch', disabled: 'inSchema', readonly: 'isEditMode',
-          group: gettext('Definition'),
-        },{
-          id: 'indisclustered', label: gettext('Clustered?'), cell: 'string',
-          type: 'switch', disabled: 'inSchema',
-          group: gettext('Definition'),
-        },{
-          id: 'indisvalid', label: gettext('Valid?'), cell: 'string',
-          type: 'switch', mode: ['properties'],
-          group: gettext('Definition'),
-        },{
-          id: 'indisprimary', label: gettext('Primary?'), cell: 'string',
-          type: 'switch', mode: ['properties'],
-          group: gettext('Definition'),
-        },{
-          id: 'is_sys_idx', label: gettext('System index?'), cell: 'string',
-          type: 'switch', mode: ['properties'],
-        },{
-          id: 'isconcurrent', label: gettext('Concurrent build?'), cell: 'string',
-          type: 'switch', disabled: 'inSchema', readonly: 'isEditMode',
-          mode: ['create', 'edit'], group: gettext('Definition'),
-        },{
-          id: 'indconstraint', label: gettext('Constraint'), cell: 'string',
-          type: 'text', disabled: 'inSchema', readonly: 'isEditMode', mode: ['create', 'edit'],
-          control: 'sql-field', visible: true, group: gettext('Definition'),
-        },{
-          id: 'columns', label: gettext('Columns'), type: 'collection', deps: ['amname'],
-          group: gettext('Definition'), model: ColumnModel, mode: ['edit', 'create'],
-          canAdd: function(m) {
-            // We will disable it if it's in 'edit' mode
-            return m.isNew();
-          },
-          canEdit: false,
-          canDelete: function(m) {
-            // We will disable it if it's in 'edit' mode
-            return m.isNew();
-          },
-          control: 'unique-col-collection', uniqueCol : ['colname'],
-          columns: ['colname', 'op_class', 'sort_order', 'nulls', 'collspcname'],
-        },{
-          id: 'include', label: gettext('Include columns'),
-          type: 'array', group: gettext('Definition'),
-          editable: false,
-          canDelete: true, canAdd: true, mode: ['edit', 'create'],
-          disabled: 'inSchema', readonly: 'isEditMode',
-          visible: function(m) {
-            if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
-              && !_.isUndefined(m.node_info.server.version) &&
-              m.node_info.server.version >= 110000)
-              return true;
-
-            return false;
-          },
-          control: Backform.MultiSelectAjaxControl.extend({
-            defaults: _.extend(
-              {},
-              Backform.NodeListByNameControl.prototype.defaults,
-              {
-                select2: {
-                  allowClear: false,
-                  width: 'style',
-                  multiple: true,
-                  placeholder: gettext('Select the column(s)'),
-                },
-              }
-            ),
-          }),
-          transform : function(data){
-            var res = [];
-            if (data && _.isArray(data)) {
-              _.each(data, function(d) {
-                res.push({label: d.label, value: d.label, image:'icon-column'});
-              });
-            }
-            return res;
-          },
-          node:'column',
-        },{
+        }, {
           id: 'description', label: gettext('Comment'), cell: 'string',
           type: 'multiline', mode: ['properties', 'create', 'edit'],
           disabled: 'inSchema',
-        },
-        ],
-        validate: function(keys) {
-          var msg;
-
-          // Nothing to validate
-          if (keys && keys.length == 0) {
-            this.errorModel.clear();
-            return null;
-          } else {
-            this.errorModel.clear();
-          }
-
-          if (_.isUndefined(this.get('name'))
-            || String(this.get('name')).replace(/^\s+|\s+$/g, '') == '') {
-            msg = gettext('Name cannot be empty.');
-            this.errorModel.set('name', msg);
-            return msg;
-          }
-          if (_.isUndefined(this.get('amname'))
-            || String(this.get('amname')).replace(/^\s+|\s+$/g, '') == '') {
-            msg = gettext('Access method cannot be empty.');
-            this.errorModel.set('amname', msg);
-            return msg;
-          }
-          // Checks if all columns has names
-          var cols = this.get('columns');
-          if(cols && cols.length > 0) {
-            if(!_.every(cols.pluck('colname'))) {
-              msg = gettext('You must specify column name.');
-              this.errorModel.set('columns', msg);
-              return msg;
-            }
-          } else if(cols){
-            msg = gettext('You must specify at least one column.');
-            this.errorModel.set('columns', msg);
-            return msg;
-          }
-          return null;
-        },
-        // We will check if we are under schema node & in 'create' mode
-        inSchema: function() {
-          if(this.node_info &&  'catalog' in this.node_info) {
-            return true;
-          }
-          return false;
-        },
-        isEditMode: function(m) {
-          return !m.isNew();
-        },
-        // We will check if we are under schema node & in 'create' mode
-        inSchemaWithModelCheck: function(m) {
-          if(this.node_info &&  'schema' in this.node_info) {
-            // We will disable control if it's in 'edit' mode
-            return !m.isNew();
-          }
-          return true;
-        },
-        // Checks weather to enable/disable control
-        inSchemaWithColumnCheck: function(m) {
-          if(this.node_info &&  'schema' in this.node_info) {
-            // We will disable control if it's system columns
-            // ie: it's position is less then 1
-            if (m.isNew()) {
-              return false;
-            } else {
-              // if we are in edit mode
-              return (_.isUndefined(m.get('attnum')) || m.get('attnum') < 1 );
-            }
-          }
-          return true;
-        },
+        }],
       }),
       // Below function will enable right click menu for creating column
       canCreate: function(itemData, item, data) {
@@ -574,6 +150,25 @@ define('pgadmin.node.index', [
           return !is_immediate_parent_table_partitioned;
         }
       },
+      getSchema: (treeNodeInfo, itemNodeData) => {
+        let nodeObj = pgAdmin.Browser.Nodes['index'];
+        return new IndexSchema(
+          ()=>getColumnSchema(nodeObj, treeNodeInfo, itemNodeData),
+          {
+            tablespaceList: ()=>getNodeListByName('tablespace', treeNodeInfo, itemNodeData, {}, (m)=>{
+              return (m.label != 'pg_global');
+            }),
+            amnameList : ()=>getNodeAjaxOptions('get_access_methods', nodeObj, treeNodeInfo, itemNodeData),
+            columnList: ()=>getNodeListByName('column', treeNodeInfo, itemNodeData, {}),
+          },
+          {
+            node_info: treeNodeInfo
+          },
+          {
+            amname: 'btree'
+          }
+        );
+      }
     });
   }
 
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/static/js/index.ui.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/static/js/index.ui.js
new file mode 100644
index 000000000..f1d1131e7
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/static/js/index.ui.js
@@ -0,0 +1,479 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import gettext from 'sources/gettext';
+import BaseUISchema from 'sources/SchemaView/base_schema.ui';
+import { getNodeAjaxOptions, getNodeListByName } from '../../../../../../../../static/js/node_ajax';
+import _ from 'lodash';
+import { pgAlertify } from  'sources/helpers/legacyConnector';
+import { isEmptyString } from 'sources/validators';
+
+export function getColumnSchema(nodeObj, treeNodeInfo, itemNodeData) {
+  return new ColumnSchema(
+    {
+      columnList: ()=>getNodeListByName('column', treeNodeInfo, itemNodeData, {}),
+      collationList: ()=>getNodeAjaxOptions('get_collations', nodeObj, treeNodeInfo, itemNodeData),
+      opClassList: ()=>getNodeAjaxOptions('get_op_class', nodeObj, treeNodeInfo, itemNodeData)
+    }, {
+      node_info: treeNodeInfo
+    }
+  );
+}
+
+export class ColumnSchema extends BaseUISchema {
+  constructor(fieldOptions = {}, nodeData, initValues) {
+    super({
+      name: null,
+      oid: undefined,
+      description: '',
+      is_sys_obj: false,
+      colname: undefined,
+      collspcname: undefined,
+      op_class: undefined,
+      sort_order: false,
+      nulls: false,
+      is_sort_nulls_applicable: true,
+      ...initValues
+    });
+    this.fieldOptions = {
+      columnList: [],
+      collationList: [],
+      opClassList: [],
+      ...fieldOptions
+    };
+    this.node_info = {
+      ...nodeData.node_info
+    };
+    this.op_class_types = [];
+  }
+
+  get idAttribute() {
+    return 'oid';
+  }
+
+  // We will check if we are under schema node & in 'create' mode
+  inSchemaWithModelCheck(state) {
+    if(this.node_info &&  'schema' in this.node_info) {
+      // We will disable control if it's in 'edit' mode
+      return !this.isNew(state);
+    }
+    return true;
+  }
+
+  setOpClassTypes(options) {
+
+    if(!options || (_.isArray(options) && options.length == 0))
+      return this.op_class_types;
+
+    if(this.op_class_types.length == 0)
+      this.op_class_types = options;
+  }
+
+  get baseFields() {
+    let columnSchemaObj = this;
+    return [
+      {
+        id: 'colname', label: gettext('Column'),
+        type: 'select', cell: 'select', noEmpty: true,
+        disabled: () => inSchema(columnSchemaObj.node_info),
+        readonly: function (state) {
+          return columnSchemaObj.inSchemaWithModelCheck(state);
+        },
+        options: columnSchemaObj.fieldOptions.columnList,
+        node: 'column',
+      },{
+        id: 'collspcname', label: gettext('Collation'),
+        type: 'select',
+        cell: 'select',
+        disabled: () => inSchema(columnSchemaObj.node_info),
+        readonly: function (state) {
+          return columnSchemaObj.inSchemaWithModelCheck(state);
+        },
+        options: columnSchemaObj.fieldOptions.collationList,
+        node: 'index',
+        url_jump_after_node: 'schema',
+      },{
+        id: 'op_class', label: gettext('Operator class'),
+        tags: true, type: 'select',
+        cell: () => {
+          return {
+            cell: 'select',
+            options: columnSchemaObj.fieldOptions.opClassList,
+            optionsLoaded: (options)=>{columnSchemaObj.setOpClassTypes(options);},
+            controlProps: {
+              allowClear: true,
+              filter: (options) => {
+                /* We need to extract data from collection according
+                  * to access method selected by user if not selected
+                  * send btree related op_class options
+                  */
+                var amname = columnSchemaObj._top._sessData ? columnSchemaObj._top._sessData.amname : columnSchemaObj._top._origData.amname;
+
+                if(_.isUndefined(amname))
+                  return options;
+
+                _.each(this.op_class_types, function(v, k) {
+                  if(amname === k) {
+                    options = v;
+                    return;
+                  }
+                });
+                return options;
+              }
+            }
+          };
+        },
+        readonly: function (state) {
+          return columnSchemaObj.inSchemaWithModelCheck(state);
+        },
+        node: 'index',
+        url_jump_after_node: 'schema',
+        deps: ['amname'],
+      },{
+        id: 'sort_order', label: gettext('Sort order'),
+        type: 'switch',
+        cell: 'switch',
+        depChange: (state, source, topState, actionObj) => {
+          //Access method is empty or btree then do not disable field
+          if(isEmptyString(topState.amname) || topState.amname === 'btree') {
+            // We need to set nulls to true if sort_order is set to desc
+            // nulls first is default for desc
+            if(state.sort_order == true && actionObj.oldState.sort_order ==  false) {
+              setTimeout(function() {
+                state.nulls = true;
+              }, 10);
+            }
+          }
+          else {
+            state.is_sort_nulls_applicable = false;
+          }
+        },
+        editable: function(state) {
+          let topObj = columnSchemaObj._top;
+          if(columnSchemaObj.inSchemaWithModelCheck(state)) {
+            return false;
+          } else if (topObj._sessData && topObj._sessData.amname === 'btree') {
+            state.is_sort_nulls_applicable = true;
+            return true;
+          } else {
+            state.is_sort_nulls_applicable = false;
+            return false;
+          }
+        },
+        deps: ['amname'],
+        options: {
+          'onText': 'DESC', 'offText': 'ASC',
+        },
+      },{
+        id: 'nulls', label: gettext('NULLs'),
+        type: 'switch',
+        cell: 'switch',
+        editable: function(state) {
+          let topObj = columnSchemaObj._top;
+          if(columnSchemaObj.inSchemaWithModelCheck(state)) {
+            return false;
+          } else if (topObj._sessData && topObj._sessData.amname === 'btree') {
+            state.is_sort_nulls_applicable = true;
+            return true;
+          } else {
+            state.is_sort_nulls_applicable = false;
+            return false;
+          }
+        },
+        deps: ['amname', 'sort_order'],
+        options: {
+          'onText': 'FIRST', 'offText': 'LAST',
+        },
+      },
+    ];
+  }
+}
+
+function inSchema(node_info) {
+  if(node_info &&  'catalog' in node_info)
+  {
+    return true;
+  }
+  return false;
+}
+
+export default class IndexSchema extends BaseUISchema {
+  constructor(getColumnSchema, fieldOptions = {}, nodeData, initValues) {
+    super({
+      name: undefined,
+      oid: undefined,
+      description: '',
+      is_sys_obj: false,
+      nspname: undefined,
+      tabname: undefined,
+      spcname: undefined,
+      amname: undefined,
+      columns: [],
+      ...initValues
+    });
+    this.fieldOptions = {
+      tablespaceList: [],
+      amnameList: [],
+      columnList: [],
+      ...fieldOptions
+    };
+    this.node_info = {
+      ...nodeData.node_info
+    };
+    this.getColumnSchema = getColumnSchema;
+  }
+
+  get idAttribute() {
+    return 'oid';
+  }
+
+  get baseFields() {
+    let indexSchemaObj = this;
+    return [
+      {
+        id: 'name', label: gettext('Name'), cell: 'string',
+        type: 'text', noEmpty: true,
+        disabled: () => inSchema(indexSchemaObj.node_info),
+      },{
+        id: 'oid', label: gettext('OID'), cell: 'string',
+        type: 'int', readonly: true, mode: ['properties'],
+      },{
+        id: 'spcname', label: gettext('Tablespace'), cell: 'string',
+        node: 'tablespace',
+        mode: ['properties', 'create', 'edit'],
+        disabled: () => inSchema(indexSchemaObj.node_info),
+        type: 'select',
+        options: indexSchemaObj.fieldOptions.tablespaceList,
+        controlProps: { allowClear: true },
+      },{
+        id: 'amname', label: gettext('Access Method'), cell: 'string',
+        mode: ['properties', 'create', 'edit'], noEmpty: true,
+        disabled: () => inSchema(indexSchemaObj.node_info),
+        readonly: function (state) {
+          return !indexSchemaObj.isNew(state);
+        },
+        url_jump_after_node: 'schema',
+        group: gettext('Definition'),
+        type: () => {
+          return {
+            type: 'select',
+            options: indexSchemaObj.fieldOptions.amnameList,
+            optionsLoaded: (options) => { indexSchemaObj.fieldOptions.amnameList = options; },
+            controlProps: {
+              allowClear: true,
+              filter: (options) => {
+                let res = [];
+                if (options && _.isArray(options)) {
+                  _.each(options, function(d) {
+                    if(d.label != '')
+                      res.push({label: d.label, value: d.value, data:d});
+                  });
+                }
+                return res;
+              }
+            }
+          };
+        },
+        deferredDepChange: (state, source, topState, actionObj) => {
+
+          const setColumns = (resolve)=>{
+            resolve(()=>{
+              state.columns.splice(0, state.columns.length);
+              return {
+                columns: state.columns,
+              };
+            });
+          };
+          if(state.amname != actionObj.oldState.amname) {
+            return new Promise((resolve)=>{
+              pgAlertify().confirm(
+                gettext('Changing access method will clear columns collection'),
+                function () {
+                  setColumns(resolve);
+                },
+                function() {
+                  resolve(()=>{
+                    state.amname = actionObj.oldState.amname;
+                    return {
+                      amname: state.amname,
+                    };
+                  });
+                }
+              );
+            });
+          } else {
+            return Promise.resolve(()=>{});
+          }
+        },
+      },{
+        id: 'columns_csv', label: gettext('Columns'), cell: 'string',
+        type: 'text',
+        disabled: () => inSchema(indexSchemaObj.node_info),
+        mode: ['properties'],
+        group: gettext('Definition'),
+      },{
+        id: 'include', label: gettext('Include columns'),
+        group: gettext('Definition'),
+        editable: false, canDelete: true, canAdd: true, mode: ['properties'],
+        disabled: () => inSchema(indexSchemaObj.node_info),
+        readonly: function (state) {
+          return !indexSchemaObj.isNew(state);
+        },
+        type: () => {
+          return {
+            type: 'select',
+            options: indexSchemaObj.fieldOptions.columnList,
+            optionsLoaded: (options) => { indexSchemaObj.fieldOptions.columnList = options; },
+            controlProps: {
+              allowClear: false,
+              multiple: true,
+              placeholder: gettext('Select the column(s)'),
+              width: 'style',
+              filter: (options) => {
+                let res = [];
+                if (options && _.isArray(options)) {
+                  _.each(options, function(d) {
+                    if(d.label != '')
+                      res.push({label: d.label, value: d.value, image:'icon-column'});
+                  });
+                }
+                return res;
+              }
+            }
+          };
+        },
+        visible: function() {
+          if(!_.isUndefined(this.node_info) && !_.isUndefined(this.node_info.server)
+            && !_.isUndefined(this.node_info.server.version) &&
+            this.node_info.server.version >= 110000)
+            return true;
+
+          return false;
+        },
+        node:'column',
+      },{
+        id: 'fillfactor', label: gettext('Fill factor'), cell: 'string',
+        type: 'int', disabled: () => inSchema(indexSchemaObj.node_info),
+        mode: ['create', 'edit', 'properties'],
+        min: 10, max:100, group: gettext('Definition'),
+      },{
+        id: 'indisunique', label: gettext('Unique?'), cell: 'string',
+        type: 'switch', disabled: () => inSchema(indexSchemaObj.node_info),
+        readonly: function (state) {
+          return !indexSchemaObj.isNew(state);
+        },
+        group: gettext('Definition'),
+      },{
+        id: 'indisclustered', label: gettext('Clustered?'), cell: 'string',
+        type: 'switch', disabled: () => inSchema(indexSchemaObj.node_info),
+        group: gettext('Definition'),
+      },{
+        id: 'indisvalid', label: gettext('Valid?'), cell: 'string',
+        type: 'switch', mode: ['properties'],
+        group: gettext('Definition'),
+      },{
+        id: 'indisprimary', label: gettext('Primary?'), cell: 'string',
+        type: 'switch', mode: ['properties'],
+        group: gettext('Definition'),
+      },{
+        id: 'is_sys_idx', label: gettext('System index?'), cell: 'string',
+        type: 'switch', mode: ['properties'],
+      },{
+        id: 'isconcurrent', label: gettext('Concurrent build?'), cell: 'string',
+        type: 'switch', disabled: () => inSchema(indexSchemaObj.node_info),
+        readonly: function (state) {
+          return !indexSchemaObj.isNew(state);
+        },
+        mode: ['create', 'edit'], group: gettext('Definition'),
+      },{
+        id: 'indconstraint', label: gettext('Constraint'), cell: 'string',
+        type: 'sql', controlProps: {className:['custom_height_css_class']},
+        disabled: () => inSchema(indexSchemaObj.node_info),
+        readonly: function (state) {
+          return !indexSchemaObj.isNew(state);
+        },
+        mode: ['create', 'edit'],
+        control: 'sql-field', visible: true, group: gettext('Definition'),
+      }, {
+        id: 'columns', label: gettext('Columns'), type: 'collection', deps: ['amname'],
+        group: gettext('Definition'), schema: indexSchemaObj.getColumnSchema(),
+        mode: ['edit', 'create'],
+        canAdd: function(state) {
+          // We will disable it if it's in 'edit' mode
+          return indexSchemaObj.isNew(state);
+        },
+        canEdit: false,
+        canDelete: function(state) {
+          // We will disable it if it's in 'edit' mode
+          return indexSchemaObj.isNew(state);
+        },
+        uniqueCol : ['colname'],
+        columns: ['colname', 'op_class', 'sort_order', 'nulls', 'collspcname']
+      }, {
+        id: 'include', label: gettext('Include columns'),
+        type: () => {
+          return {
+            type: 'select',
+            options: indexSchemaObj.fieldOptions.columnList,
+            optionsLoaded: (options) => { indexSchemaObj.fieldOptions.columnList = options; },
+            controlProps: {
+              allowClear: false,
+              multiple: true,
+              placeholder: gettext('Select the column(s)'),
+              width: 'style',
+              filter: (options) => {
+                let res = [];
+                if (options && _.isArray(options)) {
+                  _.each(options, function(d) {
+                    if(d.label != '')
+                      res.push({label: d.label, value: d.value, image:'icon-column'});
+                  });
+                }
+                return res;
+              }
+            }
+          };
+        },
+        group: gettext('Definition'),
+        editable: false,
+        canDelete: true, canAdd: true, mode: ['edit', 'create'],
+        disabled: () => inSchema(indexSchemaObj.node_info),
+        readonly: function (state) {
+          return !indexSchemaObj.isNew(state);
+        },
+        visible: function() {
+          if(!_.isUndefined(this.node_info) && !_.isUndefined(this.node_info.server)
+            && !_.isUndefined(this.node_info.server.version) &&
+            this.node_info.server.version >= 110000)
+            return true;
+
+          return false;
+        },
+        node:'column',
+      },{
+        id: 'description', label: gettext('Comment'), cell: 'string',
+        type: 'multiline', mode: ['properties', 'create', 'edit'],
+        disabled: () => inSchema(indexSchemaObj.node_info),
+      },
+    ];
+  }
+
+  validate(state, setError) {
+    var msg;
+
+    // Checks if columns is empty
+    var cols = state.columns;
+    if(_.isArray(cols) && cols.length == 0){
+      msg = gettext('You must specify at least one column.');
+      setError('columns', msg);
+      return true;
+    }
+    return null;
+  }
+}
\ No newline at end of file
diff --git a/web/regression/javascript/schema_ui_files/index.ui.spec.js b/web/regression/javascript/schema_ui_files/index.ui.spec.js
new file mode 100644
index 000000000..6f2441aaf
--- /dev/null
+++ b/web/regression/javascript/schema_ui_files/index.ui.spec.js
@@ -0,0 +1,242 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import jasmineEnzyme from 'jasmine-enzyme';
+import React from 'react';
+import '../helper/enzyme.helper';
+import { createMount } from '@material-ui/core/test-utils';
+import pgAdmin from 'sources/pgadmin';
+import {messages} from '../fake_messages';
+import SchemaView from '../../../pgadmin/static/js/SchemaView';
+import * as nodeAjax from '../../../pgadmin/browser/static/js/node_ajax';
+import IndexSchema, { getColumnSchema } from '../../../pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/static/js/index.ui';
+
+
+describe('IndexSchema', ()=>{
+  let mount;
+
+  describe('column schema describe', () => {
+
+    let columnSchemaObj = getColumnSchema({}, {server: {user: {name: 'postgres'}}}, {});
+
+    it('column schema collection', ()=>{
+
+      spyOn(nodeAjax, 'getNodeAjaxOptions').and.returnValue([]);
+      spyOn(nodeAjax, 'getNodeListByName').and.returnValue([]);
+
+      mount(<SchemaView
+        formType='dialog'
+        schema={columnSchemaObj}
+        viewHelperProps={{
+          mode: 'create',
+        }}
+        onSave={()=>{}}
+        onClose={()=>{}}
+        onHelp={()=>{}}
+        onEdit={()=>{}}
+        onDataChange={()=>{}}
+        confirmOnCloseReset={false}
+        hasSQL={false}
+        disableSqlHelp={false}
+        disableDialogHelp={false}
+      />);
+
+      mount(<SchemaView
+        formType='dialog'
+        schema={columnSchemaObj}
+        viewHelperProps={{
+          mode: 'edit',
+        }}
+        onSave={()=>{}}
+        onClose={()=>{}}
+        getInitData={getInitData}
+        onHelp={()=>{}}
+        onEdit={()=>{}}
+        onDataChange={()=>{}}
+        confirmOnCloseReset={false}
+        hasSQL={false}
+        disableSqlHelp={false}
+        disableDialogHelp={false}
+      />);
+    });
+
+    it('column schema colname editable', ()=>{
+      columnSchemaObj._top = {
+        _sessData: { amname: 'btree' }
+      };
+      let cell = _.find(columnSchemaObj.fields, (f)=>f.id=='op_class').cell;
+      cell();
+    });
+
+    it('column schema sort_order depChange', ()=>{
+      let topState = { amname: 'btree' };
+      let depChange = _.find(columnSchemaObj.fields, (f)=>f.id=='sort_order').depChange;
+
+      let state = { sort_order: true };
+      depChange(state, {}, topState, { oldState: { sort_order: false } });
+
+      state.sort_order = false;
+      topState.amname = 'abc';
+      depChange(state, {}, topState, { oldState: { sort_order: false } });
+      expect(state.is_sort_nulls_applicable).toBe(false);
+    });
+
+    it('column schema sort_order editable', ()=>{
+      columnSchemaObj._top = {
+        _sessData: { amname: 'btree' }
+      };
+      let state = {};
+      spyOn(columnSchemaObj, 'inSchemaWithModelCheck').and.returnValue(true);
+      let editable = _.find(columnSchemaObj.fields, (f)=>f.id=='sort_order').editable;
+      let status = editable(state);
+      expect(status).toBe(false);
+
+      spyOn(columnSchemaObj, 'inSchemaWithModelCheck').and.returnValue(false);
+      status = editable(state);
+      expect(status).toBe(true);
+
+      columnSchemaObj._top._sessData.amname = 'abc';
+      status = editable(state);
+      expect(status).toBe(false);
+    });
+
+    it('column schema nulls editable', ()=>{
+      columnSchemaObj._top = {
+        _sessData: { amname: 'btree' }
+      };
+      let state = {};
+      spyOn(columnSchemaObj, 'inSchemaWithModelCheck').and.returnValue(true);
+      let editable = _.find(columnSchemaObj.fields, (f)=>f.id=='nulls').editable;
+      let status = editable(state);
+      expect(status).toBe(false);
+
+      spyOn(columnSchemaObj, 'inSchemaWithModelCheck').and.returnValue(false);
+      status = editable(state);
+      expect(status).toBe(true);
+
+      columnSchemaObj._top._sessData.amname = 'abc';
+      status = editable(state);
+      expect(status).toBe(false);
+    });
+
+    it('column schema setOpClassTypes', ()=>{
+      columnSchemaObj._top = {
+        _sessData: { amname: 'btree' }
+      };
+      let options = [];
+      columnSchemaObj.op_class_types = [];
+      let status = columnSchemaObj.setOpClassTypes(options);
+      expect(status).toEqual([]);
+
+      columnSchemaObj.op_class_types = [];
+      options.push({label: '', value: ''});
+      status = columnSchemaObj.setOpClassTypes(options);
+      expect(columnSchemaObj.op_class_types.length).toBe(1);
+    });
+  });
+
+  let indexSchemaObj = new IndexSchema(
+    ()=>getColumnSchema({}, {server: {user: {name: 'postgres'}}}, {}),
+    {
+      tablespaceList: ()=>[],
+      amnameList : ()=>[{label:'abc', value:'abc'}],
+      columnList: ()=>[{label:'abc', value:'abc'}],
+    },
+    {
+      node_info: {'server': { 'version': 110000} }
+    },
+    {
+      amname: 'btree'
+    }
+  );
+  let getInitData = ()=>Promise.resolve({});
+
+  /* Use createMount so that material ui components gets the required context */
+  /* https://material-ui.com/guides/testing/#api */
+  beforeAll(()=>{
+    mount = createMount();
+  });
+
+  afterAll(() => {
+    mount.cleanUp();
+  });
+
+  beforeEach(()=>{
+    jasmineEnzyme();
+    /* messages used by validators */
+    pgAdmin.Browser = pgAdmin.Browser || {};
+    pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages;
+    pgAdmin.Browser.utils = pgAdmin.Browser.utils || {};
+  });
+
+  it('create', ()=>{
+    mount(<SchemaView
+      formType='dialog'
+      schema={indexSchemaObj}
+      viewHelperProps={{
+        mode: 'create',
+      }}
+      onSave={()=>{}}
+      onClose={()=>{}}
+      onHelp={()=>{}}
+      onEdit={()=>{}}
+      onDataChange={()=>{}}
+      confirmOnCloseReset={false}
+      hasSQL={false}
+      disableSqlHelp={false}
+      disableDialogHelp={false}
+    />);
+  });
+
+  it('edit', ()=>{
+    mount(<SchemaView
+      formType='dialog'
+      schema={indexSchemaObj}
+      getInitData={getInitData}
+      viewHelperProps={{
+        mode: 'create',
+      }}
+      onSave={()=>{}}
+      onClose={()=>{}}
+      onHelp={()=>{}}
+      onEdit={()=>{}}
+      onDataChange={()=>{}}
+      confirmOnCloseReset={false}
+      hasSQL={false}
+      disableSqlHelp={false}
+      disableDialogHelp={false}
+    />);
+  });
+
+  it('properties', ()=>{
+    mount(<SchemaView
+      formType='tab'
+      schema={indexSchemaObj}
+      getInitData={getInitData}
+      viewHelperProps={{
+        mode: 'properties',
+      }}
+      onHelp={()=>{}}
+      onEdit={()=>{}}
+    />);
+  });
+
+  it('validate', ()=>{
+    let state = { columns: [] };
+    let setError = jasmine.createSpy('setError');
+
+    indexSchemaObj.validate(state, setError);
+    expect(setError).toHaveBeenCalledWith('columns', 'You must specify at least one column.');
+
+    state.columns.push({});
+    let status = indexSchemaObj.validate(state, setError);
+    expect(status).toBe(null);
+  });
+});
+
