diff --git a/Gruntfile.js b/Gruntfile.js
index ec125f06..b4be5ed7 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -399,8 +399,8 @@ module.exports = function (grunt) {
         files: 'js/src/*.js',
         tasks: ['dist-js']
       },
-      docsjs: {
-        files: ['docs/assets/js/src/*.js'],
+      docsjs: { // watch both the source and docs js
+        files: ['js/src/*.js', 'docs/assets/js/src/*.js'],
         tasks: ['docs-js']
       },
       core: {
diff --git a/docs/material-design/test.md b/docs/material-design/test.md
index 3d80f990..7592fdfa 100644
--- a/docs/material-design/test.md
+++ b/docs/material-design/test.md
@@ -17,6 +17,7 @@ group: material-design
 {% endexample %}
 
 
+
 ## With label-placeholder
 Perhaps this isn't worth doing, considering the context.  we need to override the top calc to determine where this goes, or perhaps we should switch to a bottom calc for everything?
 {% example html %}
diff --git a/js/src/autofill.js b/js/src/autofill.js
index eba5a7a9..76d49e9b 100644
--- a/js/src/autofill.js
+++ b/js/src/autofill.js
@@ -1,3 +1,5 @@
+import Base from './base'
+
 const Autofill = (($) => {
 
   /**
@@ -17,20 +19,17 @@ const Autofill = (($) => {
    * Class Definition
    * ------------------------------------------------------------------------
    */
-  class Autofill {
+  class Autofill extends Base {
 
     constructor($element, config) {
-      this.$element = $element
-      this.config = $.extend(true, {}, Default, config)
+      super($element, $.extend(true, {}, Default, config))
 
       this._watchLoading()
       this._attachEventHandlers()
     }
 
     dispose() {
-      $.removeData(this.$element, DATA_KEY)
-      this.$element = null
-      this.config = null
+      super.dispose(DATA_KEY)
     }
 
     // ------------------------------------------------------------------------
diff --git a/js/src/base.js b/js/src/base.js
new file mode 100644
index 00000000..e957a594
--- /dev/null
+++ b/js/src/base.js
@@ -0,0 +1,95 @@
+import Util from './util'
+
+const Base = (($) => {
+
+  const ClassName = {
+    //FORM_GROUP: 'form-group',
+    MDB_FORM_GROUP: 'mdb-form-group',
+    //MDB_LABEL: 'mdb-label',
+    //MDB_LABEL_STATIC: 'mdb-label-static',
+    //MDB_LABEL_PLACEHOLDER: 'mdb-label-placeholder',
+    //MDB_LABEL_FLOATING: 'mdb-label-floating',
+    //HAS_DANGER: 'has-danger',
+    IS_FILLED: 'is-filled',
+    IS_FOCUSED: 'is-focused'
+  }
+
+  const Selector = {
+    //FORM_GROUP: `.${ClassName.FORM_GROUP}`,
+    MDB_FORM_GROUP: `.${ClassName.MDB_FORM_GROUP}`
+  }
+
+  const Default = {}
+
+  /**
+   * ------------------------------------------------------------------------
+   * Class Definition
+   * ------------------------------------------------------------------------
+   */
+  class Base {
+
+    /**
+     *
+     * @param element
+     * @param config
+     * @param properties - anything that needs to be set as this[key] = value.  Works around the need to call `super` before using `this`
+     */
+    constructor($element, config, properties = {}) {
+      this.$element = $element
+      this.config = $.extend(true, {}, Default, config)
+
+      // set properties for use in the constructor initialization
+      for (let key in properties) {
+        this[key] = properties[key]
+      }
+    }
+
+    dispose(dataKey) {
+      $.removeData(this.$element, dataKey)
+      this.$element = null
+      this.config = null
+    }
+
+    // ------------------------------------------------------------------------
+    // protected
+
+    addFormGroupFocus() {
+      if (!this.$element.prop('disabled')) {
+        this.$mdbFormGroup.addClass(ClassName.IS_FOCUSED)
+      }
+    }
+
+    removeFormGroupFocus() {
+      this.$mdbFormGroup.removeClass(ClassName.IS_FOCUSED)
+    }
+
+    removeIsFilled() {
+      this.$mdbFormGroup.removeClass(ClassName.IS_FILLED)
+    }
+
+    addIsFilled() {
+      this.$mdbFormGroup.addClass(ClassName.IS_FILLED)
+    }
+
+    // Find mdb-form-group
+    findMdbFormGroup(raiseError = true) {
+      let mfg = this.$element.closest(Selector.MDB_FORM_GROUP)
+      if (mfg.length === 0 && raiseError) {
+        $.error(`Failed to find ${Selector.MDB_FORM_GROUP} for ${Util.describe(this.$element)}`)
+      }
+      return mfg
+    }
+
+    // ------------------------------------------------------------------------
+    // private
+
+    // ------------------------------------------------------------------------
+    // static
+
+  }
+
+  return Base
+
+})(jQuery)
+
+export default Base
diff --git a/js/src/baseInput.js b/js/src/baseInput.js
index 028a07d9..2fae5a8e 100644
--- a/js/src/baseInput.js
+++ b/js/src/baseInput.js
@@ -1,3 +1,4 @@
+import Base from './base'
 import Util from './util'
 
 const BaseInput = (($) => {
@@ -26,7 +27,7 @@ const BaseInput = (($) => {
       required: false
     },
     mdbFormGroup: {
-      template: `<span class='mdb-form-group'></span>`,
+      template: `<span class='${ClassName.MDB_FORM_GROUP}'></span>`,
       create: true, // create a wrapper if form-group not found
       required: true // not recommended to turn this off, only used for inline components
     },
@@ -60,7 +61,7 @@ const BaseInput = (($) => {
    * Class Definition
    * ------------------------------------------------------------------------
    */
-  class BaseInput {
+  class BaseInput extends Base {
 
     /**
      *
@@ -69,13 +70,7 @@ const BaseInput = (($) => {
      * @param properties - anything that needs to be set as this[key] = value.  Works around the need to call `super` before using `this`
      */
     constructor($element, config, properties = {}) {
-      this.$element = $element
-      this.config = $.extend(true, {}, Default, config)
-
-      // set properties for use in the constructor initialization
-      for (let key in properties) {
-        this[key] = properties[key]
-      }
+      super($element, $.extend(true, {}, Default, config), properties)
 
       // Enforce no overlap between components to prevent side effects
       this._rejectInvalidComponentMatches()
@@ -106,10 +101,9 @@ const BaseInput = (($) => {
     }
 
     dispose(dataKey) {
-      $.removeData(this.$element, dataKey)
-      this.$element = null
+      super.dispose(dataKey)
       this.$mdbFormGroup = null
-      this.config = null
+      this.$formGroup = null
     }
 
     // ------------------------------------------------------------------------
@@ -163,16 +157,6 @@ const BaseInput = (($) => {
         })
     }
 
-    addFormGroupFocus() {
-      if (!this.$element.prop('disabled')) {
-        this.$mdbFormGroup.addClass(ClassName.IS_FOCUSED)
-      }
-    }
-
-    removeFormGroupFocus() {
-      this.$mdbFormGroup.removeClass(ClassName.IS_FOCUSED)
-    }
-
     addHasDanger() {
       this.$mdbFormGroup.addClass(ClassName.HAS_DANGER)
     }
@@ -181,14 +165,6 @@ const BaseInput = (($) => {
       this.$mdbFormGroup.removeClass(ClassName.HAS_DANGER)
     }
 
-    removeIsFilled() {
-      this.$mdbFormGroup.removeClass(ClassName.IS_FILLED)
-    }
-
-    addIsFilled() {
-      this.$mdbFormGroup.addClass(ClassName.IS_FILLED)
-    }
-
     isEmpty() {
       return (this.$element.val() === null || this.$element.val() === undefined || this.$element.val() === '')
     }
@@ -264,15 +240,6 @@ const BaseInput = (($) => {
       return label
     }
 
-    // Find mdb-form-group
-    findMdbFormGroup(raiseError = true) {
-      let mfg = this.$element.closest(Selector.MDB_FORM_GROUP)
-      if (mfg.length === 0 && raiseError) {
-        $.error(`Failed to find ${Selector.MDB_FORM_GROUP} for ${Util.describe(this.$element)}`)
-      }
-      return mfg
-    }
-
     // Find mdb-form-group
     findFormGroup(raiseError = true) {
       let fg = this.$element.closest(Selector.FORM_GROUP)
diff --git a/js/src/bootstrapMaterialDesign.js b/js/src/bootstrapMaterialDesign.js
index 3f679e70..89e773eb 100644
--- a/js/src/bootstrapMaterialDesign.js
+++ b/js/src/bootstrapMaterialDesign.js
@@ -37,6 +37,27 @@ const BootstrapMaterialDesign = (($) => {
         className: 'mdb-label-static' // default style of label to be used if not specified in the html markup
       }
     },
+    autofill: {
+      selector: 'body'
+    },
+    checkbox: {
+      selector: '.checkbox > label > input[type=checkbox]'
+    },
+    checkboxInline: {
+      selector: 'label.checkbox-inline > input[type=checkbox]'
+    },
+    collapseInline: {
+      selector: '.mdb-collapse-inline [data-toggle="collapse"]'
+    },
+    file: {
+      selector: 'input[type=file]'
+    },
+    radio: {
+      selector: '.radio > label > input[type=radio]'
+    },
+    radioInline: {
+      selector: 'label.radio-inline > input[type=radio]'
+    },
     ripples: {
       //selector: ['.btn:not(.btn-link):not(.ripple-none)'] // testing only
       selector: [
@@ -49,43 +70,26 @@ const BootstrapMaterialDesign = (($) => {
         '.ripple' // generic marker class to add ripple to elements
       ]
     },
-    text: {
-      // omit inputs we have specialized components to handle
-      selector: [`input[type!='checkbox'][type!='radio'][type!='file']`]
-    },
-    file: {
-      selector: 'input[type=file]'
-    },
-    checkbox: {
-      selector: '.checkbox > label > input[type=checkbox]'
-    },
-    checkboxInline: {
-      selector: 'label.checkbox-inline > input[type=checkbox]'
-    },
-    radio: {
-      selector: '.radio > label > input[type=radio]'
-    },
-    radioInline: {
-      selector: 'label.radio-inline > input[type=radio]'
-    },
     select: {
       selector: ['select']
     },
     switch: {
       selector: '.switch > label > input[type=checkbox]'
     },
+    text: {
+      // omit inputs we have specialized components to handle
+      selector: [`input[type!='checkbox'][type!='radio'][type!='file']`]
+    },
     textarea: {
       selector: ['textarea']
     },
-    autofill: {
-      selector: 'body'
-    },
     arrive: true,
     // create an ordered component list for instantiation
     instantiation: [
       'ripples',
       'checkbox',
       'checkboxInline',
+      'collapseInline',
       'file',
       'radio',
       'radioInline',
diff --git a/js/src/checkbox.js b/js/src/checkbox.js
index 84969414..05e4d21c 100644
--- a/js/src/checkbox.js
+++ b/js/src/checkbox.js
@@ -1,9 +1,9 @@
 import BaseSelection from './baseSelection'
-import Text from './text'
-import File from './file'
-import Radio from './radio'
-import Textarea from './textarea'
-import Select from './select'
+//import Text from './text'
+//import File from './file'
+//import Radio from './radio'
+//import Textarea from './textarea'
+//import Select from './select'
 import Util from './util'
 
 const Checkbox = (($) => {
@@ -30,9 +30,9 @@ const Checkbox = (($) => {
   class Checkbox extends BaseSelection {
 
     constructor($element, config, properties = {inputType: NAME, outerClass: NAME}) {
-      super($element, $.extend(true, {
-        invalidComponentMatches: [File, Radio, Text, Textarea, Select]
-      }, Default, config), properties)
+      super($element, $.extend(true,
+        //{invalidComponentMatches: [File, Radio, Text, Textarea, Select]},
+        Default, config), properties)
     }
 
     dispose(dataKey = DATA_KEY) {
diff --git a/js/src/collapseInline.js b/js/src/collapseInline.js
new file mode 100644
index 00000000..3c6efb03
--- /dev/null
+++ b/js/src/collapseInline.js
@@ -0,0 +1,113 @@
+import Base from './base'
+import Util from './util'
+
+const CollapseInline = (($) => {
+
+  /**
+   * ------------------------------------------------------------------------
+   * Constants
+   * ------------------------------------------------------------------------
+   */
+  const NAME = 'collapseInline'
+  const DATA_KEY = `mdb.${NAME}`
+  const JQUERY_NAME = `mdb${NAME.charAt(0).toUpperCase() + NAME.slice(1)}`
+  const JQUERY_NO_CONFLICT = $.fn[JQUERY_NAME]
+
+  const Selector = {
+    ANY_INPUT: 'input, select, textarea'
+  }
+
+  const ClassName = {
+    IN: 'in',
+    COLLAPSE: 'collapse',
+    COLLAPSING: 'collapsing',
+    COLLAPSED: 'collapsed',
+    WIDTH: 'width'
+  }
+  const Default = {}
+
+  /**
+   * ------------------------------------------------------------------------
+   * Class Definition
+   * ------------------------------------------------------------------------
+   */
+  class CollapseInline extends Base {
+
+    // $element is expected to be the trigger
+    //  i.e. <button class="btn mdb-btn-icon" for="search" data-toggle="collapse" data-target="#search-field" aria-expanded="false" aria-controls="search-field">
+    constructor($element, config) {
+      super($element, $.extend(true, {}, Default, config))
+      this.$mdbFormGroup = this.findMdbFormGroup(true)
+
+      let collapseSelector = $element.data('target')
+      this.$collapse = $(collapseSelector)
+
+      Util.assert($element, this.$collapse.length === 0, `Cannot find collapse target for ${Util.describe($element)}`)
+      Util.assert(this.$collapse, !this.$collapse.hasClass(ClassName.COLLAPSE), `${Util.describe(this.$collapse)} is expected to have the '${ClassName.COLLAPSE}' class.  It is being targeted by ${Util.describe($element)}`)
+
+      // find the first input for focusing
+      let $inputs = this.$mdbFormGroup.find(Selector.ANY_INPUT)
+      if ($inputs.length > 0) {
+        this.$input = $inputs.first()
+      }
+
+      // automatically add the marker class to collapse width instead of height - nice convenience because it is easily forgotten
+      if (!this.$collapse.hasClass(ClassName.WIDTH)) {
+        this.$collapse.addClass(ClassName.WIDTH)
+      }
+
+      if (this.$input) {
+        // add a listener to set focus
+        this.$collapse.on('shown.bs.collapse', () => {
+          this.$input.focus()
+        })
+
+        // add a listener to collapse field
+        this.$input.blur(() => {
+          this.$collapse.collapse('hide')
+        })
+      }
+    }
+
+    dispose() {
+      super.dispose(DATA_KEY)
+      this.$mdbFormGroup = null
+      this.$collapse = null
+      this.$input = null
+    }
+
+    // ------------------------------------------------------------------------
+    // private
+
+    // ------------------------------------------------------------------------
+    // static
+    static _jQueryInterface(config) {
+      return this.each(function () {
+        let $element = $(this)
+        let data = $element.data(DATA_KEY)
+
+        if (!data) {
+          data = new CollapseInline($element, config)
+          $element.data(DATA_KEY, data)
+        }
+      })
+    }
+  }
+
+  /**
+   * ------------------------------------------------------------------------
+   * jQuery
+   * ------------------------------------------------------------------------
+   */
+  $.fn[JQUERY_NAME] = CollapseInline._jQueryInterface
+  $.fn[JQUERY_NAME].Constructor = CollapseInline
+  $.fn[JQUERY_NAME].noConflict = () => {
+    $.fn[JQUERY_NAME] = JQUERY_NO_CONFLICT
+    return CollapseInline._jQueryInterface
+  }
+
+  return CollapseInline
+
+})(jQuery)
+
+export default CollapseInline
diff --git a/js/src/file.js b/js/src/file.js
index 6316e6e1..cfd65b05 100644
--- a/js/src/file.js
+++ b/js/src/file.js
@@ -1,10 +1,10 @@
 import BaseInput from './baseInput'
-import Checkbox from './checkbox'
-import Radio from './radio'
-import Switch from './switch'
-import Text from './text'
-import Textarea from './textarea'
-import Select from './select'
+//import Checkbox from './checkbox'
+//import Radio from './radio'
+//import Switch from './switch'
+//import Text from './text'
+//import Textarea from './textarea'
+//import Select from './select'
 import Util from './util'
 
 const File = (($) => {
@@ -38,7 +38,9 @@ const File = (($) => {
   class File extends BaseInput {
 
     constructor($element, config) {
-      super($element, $.extend(true, {invalidComponentMatches: [Checkbox, Radio, Text, Textarea, Select, Switch]}, Default, config))
+      super($element, $.extend(true,
+        //{invalidComponentMatches: [Checkbox, Radio, Text, Textarea, Select, Switch]},
+        Default, config))
 
       this.$mdbFormGroup.addClass(ClassName.IS_FILE)
     }
diff --git a/js/src/index.js b/js/src/index.js
index 701a276d..0efbcbe6 100644
--- a/js/src/index.js
+++ b/js/src/index.js
@@ -10,20 +10,19 @@
 /* eslint-disable no-unused-vars */
 import '../../node_modules/babel-polyfill/dist/polyfill'
 
-import BaseInput from './baseInput'
-import BaseFormControl from './baseFormControl'
-import BaseSelection from './baseSelection'
-import Util from './util'
-import Ripples from './ripples'
-import Autofill from './autofill'
-import Text from './text'
-import Textarea from './textarea'
-import Select from './select'
+// invalidComponentMatches is currently disabled due to https://github.com/rollup/rollup/issues/428#issuecomment-170066452
 import Checkbox from './checkbox'
 import CheckboxInline from './checkboxInline'
-import Switch from './switch'
+import CollapseInline from './collapseInline'
+import File from './file'
 import Radio from './radio'
 import RadioInline from './radioInline'
-import File from './file'
+import Select from './select'
+import Switch from './switch'
+import Text from './text'
+import Textarea from './textarea'
+
+import Ripples from './ripples'
+import Autofill from './autofill'
 import BootstrapMaterialDesign from './bootstrapMaterialDesign'
 /* eslint-enable no-unused-vars */
diff --git a/js/src/radio.js b/js/src/radio.js
index 92cdfb21..b6c7c9ac 100644
--- a/js/src/radio.js
+++ b/js/src/radio.js
@@ -1,8 +1,8 @@
 import BaseSelection from './baseSelection'
-import Text from './text'
-import File from './file'
-import Checkbox from './checkbox'
-import Switch from './switch'
+//import Text from './text'
+//import File from './file'
+//import Checkbox from './checkbox'
+//import Switch from './switch'
 import Util from './util'
 
 const Radio = (($) => {
@@ -29,9 +29,9 @@ const Radio = (($) => {
   class Radio extends BaseSelection {
 
     constructor($element, config, properties = {inputType: NAME, outerClass: NAME}) {
-      super($element, $.extend(true, {
-        invalidComponentMatches: [Checkbox, File, Switch, Text]
-      }, Default, config), properties)
+      super($element, $.extend(true,
+        //{invalidComponentMatches: [Checkbox, File, Switch, Text]},
+        Default, config), properties)
     }
 
     dispose(dataKey = DATA_KEY) {
diff --git a/js/src/select.js b/js/src/select.js
index 6bc60b66..cf7a1447 100644
--- a/js/src/select.js
+++ b/js/src/select.js
@@ -1,10 +1,10 @@
 import BaseFormControl from './baseFormControl'
-import Checkbox from './checkbox'
-import File from './file'
-import Radio from './radio'
-import Switch from './switch'
-import Text from './text'
-import Textarea from './textarea'
+//import Checkbox from './checkbox'
+//import File from './file'
+//import Radio from './radio'
+//import Switch from './switch'
+//import Text from './text'
+//import Textarea from './textarea'
 import Util from './util'
 
 const Select = (($) => {
@@ -31,7 +31,9 @@ const Select = (($) => {
   class Select extends BaseFormControl {
 
     constructor($element, config) {
-      super($element, $.extend(true, {invalidComponentMatches: [Checkbox, File, Radio, Switch, Text, Textarea]}, Default, config))
+      super($element, $.extend(true,
+        //{invalidComponentMatches: [Checkbox, File, Radio, Switch, Text, Textarea]},
+        Default, config))
 
       // floating labels will cover the options, so trigger them to be above (if used)
       this.addIsFilled()
diff --git a/js/src/text.js b/js/src/text.js
index 38f55c74..ca58175f 100644
--- a/js/src/text.js
+++ b/js/src/text.js
@@ -1,10 +1,10 @@
 import BaseFormControl from './baseFormControl'
-import Checkbox from './checkbox'
-import File from './file'
-import Radio from './radio'
-import Switch from './switch'
-import Textarea from './textarea'
-import Select from './select'
+//import Checkbox from './checkbox'
+//import File from './file'
+//import Radio from './radio'
+//import Switch from './switch'
+//import Textarea from './textarea'
+//import Select from './select'
 import Util from './util'
 
 const Text = (($) => {
@@ -29,7 +29,9 @@ const Text = (($) => {
   class Text extends BaseFormControl {
 
     constructor($element, config) {
-      super($element, $.extend(true, {invalidComponentMatches: [Checkbox, File, Radio, Switch, Select, Textarea]}, Default, config))
+      super($element, $.extend(true,
+        //{invalidComponentMatches: [Checkbox, File, Radio, Switch, Select, Textarea]},
+        Default, config))
     }
 
     dispose(dataKey = DATA_KEY) {
diff --git a/js/src/textarea.js b/js/src/textarea.js
index 9093df6b..205483b1 100644
--- a/js/src/textarea.js
+++ b/js/src/textarea.js
@@ -1,10 +1,10 @@
 import BaseFormControl from './baseFormControl'
-import Checkbox from './checkbox'
-import File from './file'
-import Radio from './radio'
-import Switch from './switch'
-import Text from './text'
-import Select from './select'
+//import Checkbox from './checkbox'
+//import File from './file'
+//import Radio from './radio'
+//import Switch from './switch'
+//import Text from './text'
+//import Select from './select'
 import Util from './util'
 
 const Textarea = (($) => {
@@ -29,7 +29,9 @@ const Textarea = (($) => {
   class Textarea extends BaseFormControl {
 
     constructor($element, config) {
-      super($element, $.extend(true, {invalidComponentMatches: [Checkbox, File, Radio, Text, Select, Switch]}, Default, config))
+      super($element, $.extend(true,
+        //{invalidComponentMatches: [Checkbox, File, Radio, Text, Select, Switch]},
+        Default, config))
     }
 
     dispose() {