diff --git a/core/js/Component/Transliteration.es6.js b/core/js/Component/Transliteration.es6.js
new file mode 100644
index 0000000..dea530f
--- /dev/null
+++ b/core/js/Component/Transliteration.es6.js
@@ -0,0 +1,57 @@
+
+/**
+ * Transliteration service.
+ */
+class Transliteration {
+
+  /**
+   * Constructor
+   *
+   * @param {object} options
+   *   - replace_pattern: A regular expression (without modifiers) matching
+   *     disallowed characters in the machine name; e.g., '[^a-z0-9]+'.
+   *   - replace: A character to replace disallowed characters with; e.g.,
+   *     '_' or '-'.
+   *   - max_length: The maximum length of the transliterated value (default: 10).
+   */
+  constructor(options = {replace_pattern: '[^a-z0-9_]+', replace: '_', max_length: 10}) {
+    this.replace_pattern = options.replace_pattern;
+    this.replace = options.replace;
+    this.max_length = options.max_length;
+  }
+
+  /**
+   * Convert a human-readable name to a machine name.
+   *
+   * @param {string} source
+   *   A string to transliterate.
+   * @param {function} callback
+   *   The callback to run once the source is converted to a machine name.
+   */
+  convert(source, callback = function (result) {}) {
+    const rx = new RegExp(this.replace_pattern, 'g');
+    const expected = source.toLowerCase().replace(rx, this.replace).substr(0, this.max_length);
+
+    // Abort the last pending request because the label has changed and it
+    // is no longer valid.
+    if (this.xhr && this.xhr.readystate !== 4) {
+      this.xhr.abort();
+      this.xhr = null;
+    }
+
+    if (source.toLowerCase() !== expected) {
+      this.xhr = jQuery.get(Drupal.url('machine_name/transliterate'), {
+        text: source,
+        langcode: drupalSettings.langcode,
+        replace_pattern: this.replace_pattern,
+        replace: this.replace,
+        lowercase: true
+      }, (response) => callback(response));
+    }
+    else {
+      callback(expected);
+    }
+  }
+}
+
+export default Transliteration;
\ No newline at end of file
diff --git a/core/js/Render/Element/MachineName.es6.js b/core/js/Render/Element/MachineName.es6.js
new file mode 100644
index 0000000..03c00b8
--- /dev/null
+++ b/core/js/Render/Element/MachineName.es6.js
@@ -0,0 +1,163 @@
+
+import Transliteration from '/core/js/Component/Transliteration.es6';
+
+/**
+ * Constructor for the machine name.
+ */
+class MachineName {
+
+  /**
+   * Constructor.
+   *
+   * @param {object} options
+   *   The options.
+   */
+  constructor(options = {
+    target: '',
+    suffix: '',
+    max_length: null,
+    field_prefix: '',
+    field_suffix: '',
+    label: ''
+  }) {
+    this.options = options;
+
+    this.transliteration = new Transliteration(options);
+  }
+
+  /**
+   * Attach the UI to the source field within the provided context.
+   *
+   * @param {jQuery} $context
+   *   The jQuery object the machine name field is contained in.
+   * @param {jQuery} $source
+   *   The jQuery object for the source field.
+   */
+  attachUI($context = jQuery, $source = jQuery) {
+    this.$context = $context;
+    this.$source = $source;
+
+    this.$target = this.$context.find(this.options.target).addClass('machine-name-target');
+    this.$suffix = this.$context.find(this.options.suffix);
+    this.$wrapper = this.$target.closest('.js-form-item');
+
+    // All elements have to exist.
+    if (!this.$source.length || !this.$target.length || !this.$suffix.length || !this.$wrapper.length) {
+      return;
+    }
+    // Skip processing upon a form validation error on the machine name.
+    if (this.$target.hasClass('error')) {
+      return;
+    }
+
+    this.$source.addClass('machine-name-source');
+
+    // Figure out the maximum length for the machine name.
+    if (!this.options.max_length) {
+      this.options.max_length = this.$target.attr('maxlength');
+    }
+
+    // Hide the form item container of the machine name form element.
+    this.$wrapper.addClass('visually-hidden');
+
+    // Determine the initial machine name value. Unless the machine name
+    // form element is disabled or not empty, the initial default value is
+    // based on the human-readable form element value.
+    if (this.$target.is(':disabled') || this.$target.val() !== '') {
+      this.addSuffix(this.$target.val());
+    }
+    else if (this.$source.val() !== '') {
+      this.transliteration.convert(this.$source.val(), (result) => this.addSuffix(result));
+    }
+
+    // If the machine name cannot be edited, stop further processing.
+    if (this.$target.is(':disabled')) {
+      return;
+    }
+
+    // Preview the machine name in realtime when the human-readable name
+    // changes, but only if there is no machine name yet; i.e., only upon
+    // initial creation, not when editing.
+    if (this.$target.val() === '') {
+      // Initialize machine name preview.
+      this.update();
+    }
+
+    // Add listener to the formUpdated to update the machine name.
+    this.$source.on('formUpdated.machineName', this, () => this.update());
+
+    // Add a listener for an invalid event on the machine name input
+    // to show its container and focus it.
+    this.$target.on('invalid', this, () => this.detachUI());
+  }
+
+  /**
+   * Set the suffix for the field.
+   *
+   * @param {string} machine_name
+   *  The machine name to set.
+   */
+  addSuffix(machine_name = '') {
+    this.$preview = jQuery('<span class="machine-name-value">' + this.options.field_prefix + Drupal.checkPlain(machine_name) + this.options.field_suffix + '</span>');
+    this.$suffix.empty();
+
+    if (this.options.label) {
+      this.$preview.prepend(jQuery('<span class="machine-name-label">' + this.options.label + ': </span>'));
+    }
+    this.$suffix.append(this.$preview);
+
+    // If it is editable, append an edit link.
+    const $link = jQuery('<span class="admin-link"><button type="button" class="link">' + Drupal.t('Edit') + '</button></span>').on('click', () => this.detachUI());
+    this.$suffix.append($link);
+
+  }
+
+  /**
+   * Destroy the machineName instance and revert the field to it's original state.
+   */
+  detachUI() {
+    if (this.$wrapper) {
+      this.$wrapper.removeClass('visually-hidden');
+      this.$target.trigger('focus');
+      this.$suffix.hide();
+      this.$source.off('.machineName');
+    }
+  }
+
+  /**
+   * Act on a machine name.
+   *
+   * Either shows the machine name or if empty, hide the machine name container.
+   *
+   * @param {string} machine
+   *   The machine name.
+   */
+  showMachineName(machine) {
+    // Set the machine name to the transliterated value.
+    if (machine !== '') {
+      if (machine !== this.options.replace) {
+        this.$target.val(machine);
+        this.$preview.html(this.options.field_prefix + Drupal.checkPlain(machine) + this.options.field_suffix);
+      }
+      this.$suffix.show();
+    }
+    else {
+      this.$suffix.hide();
+      this.$target.val(machine);
+      this.$preview.empty();
+    }
+  }
+
+  /**
+   * Act on a change on the input field and update the machine name.
+   */
+  update() {
+    const baseValue = this.$source.val();
+
+    this.transliteration.convert(baseValue, (machine) => {
+      this.showMachineName(machine.substr(0, this.options.max_length));
+    });
+  }
+}
+
+export default MachineName;
diff --git a/core/misc/machine-name.js b/core/misc/machine-name.js
index e76292e..600b8bd 100644
--- a/core/misc/machine-name.js
+++ b/core/misc/machine-name.js
@@ -1,13 +1,222 @@
-/**
- * @file
- * Machine name functionality.
- */
 
 (function ($, Drupal, drupalSettings) {
-
   'use strict';
 
   /**
+   * Transliteration service.
+   */
+  class Transliteration {
+
+    /**
+     * Constructor
+     *
+     * @param {object} options
+     *   - replace_pattern: A regular expression (without modifiers) matching
+     *     disallowed characters in the machine name; e.g., '[^a-z0-9]+'.
+     *   - replace: A character to replace disallowed characters with; e.g.,
+     *     '_' or '-'.
+     *   - max_length: The maximum length of the transliterated value (default: 10).
+     */
+    constructor(options = {replace_pattern: '[^a-z0-9_]+', replace: '_', max_length: 10}) {
+      this.replace_pattern = options.replace_pattern;
+      this.replace = options.replace;
+      this.max_length = options.max_length;
+    }
+
+    /**
+     * Convert a human-readable name to a machine name.
+     *
+     * @param {string} source
+     *   A string to transliterate.
+     * @param {function} callback
+     *   The callback to run once the source is converted to a machine name.
+     */
+    convert(source, callback = function (result) {}) {
+      const rx = new RegExp(this.replace_pattern, 'g');
+      const expected = source.toLowerCase().replace(rx, this.replace).substr(0, this.max_length);
+
+      // Abort the last pending request because the label has changed and it
+      // is no longer valid.
+      if (this.xhr && this.xhr.readystate !== 4) {
+        this.xhr.abort();
+        this.xhr = null;
+      }
+
+      if (source.toLowerCase() !== expected) {
+        this.xhr = jQuery.get(Drupal.url('machine_name/transliterate'), {
+          text: source,
+          langcode: drupalSettings.langcode,
+          replace_pattern: this.replace_pattern,
+          replace: this.replace,
+          lowercase: true
+        }, (response) => callback(response));
+      }
+      else {
+        callback(expected);
+      }
+    }
+  }
+
+  /**
+   * Constructor for the machine name.
+   */
+  class MachineName {
+
+    /**
+     * Constructor.
+     *
+     * @param {object} options
+     *   The options.
+     */
+    constructor(options = {
+      target: '',
+      suffix: '',
+      max_length: null,
+      field_prefix: '',
+      field_suffix: '',
+      label: ''
+    }) {
+      this.options = options;
+
+      this.transliteration = new Transliteration(options);
+    }
+
+    /**
+     * Attach the UI to the source field within the provided context.
+     *
+     * @param {jQuery} $context
+     *   The jQuery object the machine name field is contained in.
+     * @param {jQuery} $source
+     *   The jQuery object for the source field.
+     */
+    attachUI($context = jQuery, $source = jQuery) {
+      this.$context = $context;
+      this.$source = $source;
+
+      this.$target = this.$context.find(this.options.target).addClass('machine-name-target');
+      this.$suffix = this.$context.find(this.options.suffix);
+      this.$wrapper = this.$target.closest('.js-form-item');
+
+      // All elements have to exist.
+      if (!this.$source.length || !this.$target.length || !this.$suffix.length || !this.$wrapper.length) {
+        return;
+      }
+      // Skip processing upon a form validation error on the machine name.
+      if (this.$target.hasClass('error')) {
+        return;
+      }
+
+      this.$source.addClass('machine-name-source');
+
+      // Figure out the maximum length for the machine name.
+      if (!this.options.max_length) {
+        this.options.max_length = this.$target.attr('maxlength');
+      }
+
+      // Hide the form item container of the machine name form element.
+      this.$wrapper.addClass('visually-hidden');
+
+      // Determine the initial machine name value. Unless the machine name
+      // form element is disabled or not empty, the initial default value is
+      // based on the human-readable form element value.
+      if (this.$target.is(':disabled') || this.$target.val() !== '') {
+        this.addSuffix(this.$target.val());
+      }
+      else if (this.$source.val() !== '') {
+        this.transliteration.convert(this.$source.val(), (result) => this.addSuffix(result));
+      }
+
+      // If the machine name cannot be edited, stop further processing.
+      if (this.$target.is(':disabled')) {
+        return;
+      }
+
+      // Preview the machine name in realtime when the human-readable name
+      // changes, but only if there is no machine name yet; i.e., only upon
+      // initial creation, not when editing.
+      if (this.$target.val() === '') {
+        // Initialize machine name preview.
+        this.update();
+      }
+
+      // Add listener to the formUpdated to update the machine name.
+      this.$source.on('formUpdated.machineName', this, () => this.update());
+
+      // Add a listener for an invalid event on the machine name input
+      // to show its container and focus it.
+      this.$target.on('invalid', this, () => this.detachUI());
+    }
+
+    /**
+     * Set the suffix for the field.
+     *
+     * @param {string} machine_name
+     *  The machine name to set.
+     */
+    addSuffix(machine_name = '') {
+      this.$preview = $('<span class="machine-name-value">' + this.options.field_prefix + Drupal.checkPlain(machine_name) + this.options.field_suffix + '</span>');
+      this.$suffix.empty();
+
+      if (this.options.label) {
+        this.$preview.prepend($('<span class="machine-name-label">' + this.options.label + ': </span>'));
+      }
+      this.$suffix.append(this.$preview);
+
+      // If it is editable, append an edit link.
+      const $link = $('<span class="admin-link"><button type="button" class="link">' + Drupal.t('Edit') + '</button></span>').on('click', () => this.detachUI());
+      this.$suffix.append($link);
+
+    }
+
+    /**
+     * Destroy the machineName instance and revert the field to it's original state.
+     */
+    detachUI() {
+      if (this.$wrapper) {
+        this.$wrapper.removeClass('visually-hidden');
+        this.$target.trigger('focus');
+        this.$suffix.hide();
+        this.$source.off('.machineName');
+      }
+    }
+
+    /**
+     * Act on a machine name.
+     *
+     * Either shows the machine name or if empty, hide the machine name container.
+     *
+     * @param {string} machine
+     *   The machine name.
+     */
+    showMachineName(machine) {
+      // Set the machine name to the transliterated value.
+      if (machine !== '') {
+        if (machine !== this.options.replace) {
+          this.$target.val(machine);
+          this.$preview.html(this.options.field_prefix + Drupal.checkPlain(machine) + this.options.field_suffix);
+        }
+        this.$suffix.show();
+      }
+      else {
+        this.$suffix.hide();
+        this.$target.val(machine);
+        this.$preview.empty();
+      }
+    }
+
+    /**
+     * Act on a change on the input field and update the machine name.
+     */
+    update() {
+      const baseValue = this.$source.val();
+
+      this.transliteration.convert(baseValue, (machine) => {
+        this.showMachineName(machine.substr(0, this.options.max_length));
+      });
+    }
+  }
+
+  /**
    * Attach the machine-readable name form element behavior.
    *
    * @type {Drupal~behavior}
@@ -43,167 +252,28 @@
      *   - field_suffix: The #field_suffix of the form element.
      */
     attach: function (context, settings) {
-      var self = this;
-      var $context = $(context);
-      var timeout = null;
-      var xhr = null;
-
-      function clickEditHandler(e) {
-        var data = e.data;
-        data.$wrapper.removeClass('visually-hidden');
-        data.$target.trigger('focus');
-        data.$suffix.hide();
-        data.$source.off('.machineName');
-      }
-
-      function machineNameHandler(e) {
-        var data = e.data;
-        var options = data.options;
-        var baseValue = $(e.target).val();
-
-        var rx = new RegExp(options.replace_pattern, 'g');
-        var expected = baseValue.toLowerCase().replace(rx, options.replace).substr(0, options.maxlength);
-
-        // Abort the last pending request because the label has changed and it
-        // is no longer valid.
-        if (xhr && xhr.readystate !== 4) {
-          xhr.abort();
-          xhr = null;
-        }
-
-        // Wait 300 milliseconds for Ajax request since the last event to update
-        // the machine name i.e., after the user has stopped typing.
-        if (timeout) {
-          clearTimeout(timeout);
-          timeout = null;
-        }
-        if (baseValue.toLowerCase() !== expected) {
-          timeout = setTimeout(function () {
-            xhr = self.transliterate(baseValue, options).done(function (machine) {
-              self.showMachineName(machine.substr(0, options.maxlength), data);
-            });
-          }, 300);
-        }
-        else {
-          self.showMachineName(expected, data);
-        }
-      }
+      const $context = $(context);
 
       Object.keys(settings.machineName).forEach(function (source_id) {
-        var machine = '';
-        var eventData;
-        var options = settings.machineName[source_id];
-
-        var $source = $context.find(source_id).addClass('machine-name-source').once('machine-name');
-        var $target = $context.find(options.target).addClass('machine-name-target');
-        var $suffix = $context.find(options.suffix);
-        var $wrapper = $target.closest('.js-form-item');
-        // All elements have to exist.
-        if (!$source.length || !$target.length || !$suffix.length || !$wrapper.length) {
-          return;
-        }
-        // Skip processing upon a form validation error on the machine name.
-        if ($target.hasClass('error')) {
-          return;
-        }
-        // Figure out the maximum length for the machine name.
-        options.maxlength = $target.attr('maxlength');
-        // Hide the form item container of the machine name form element.
-        $wrapper.addClass('visually-hidden');
-        // Determine the initial machine name value. Unless the machine name
-        // form element is disabled or not empty, the initial default value is
-        // based on the human-readable form element value.
-        if ($target.is(':disabled') || $target.val() !== '') {
-          machine = $target.val();
-        }
-        else if ($source.val() !== '') {
-          machine = self.transliterate($source.val(), options);
-        }
-        // Append the machine name preview to the source field.
-        var $preview = $('<span class="machine-name-value">' + options.field_prefix + Drupal.checkPlain(machine) + options.field_suffix + '</span>');
-        $suffix.empty();
-        if (options.label) {
-          $suffix.append('<span class="machine-name-label">' + options.label + ': </span>');
-        }
-        $suffix.append($preview);
+        if (settings.machineName.hasOwnProperty(source_id)) {
+          const $source = $context.find(source_id).once('machine-name');
 
-        // If the machine name cannot be edited, stop further processing.
-        if ($target.is(':disabled')) {
-          return;
+          if ($source.length) {
+            settings.machineName[source_id].instance = new MachineName(settings.machineName[source_id]);
+            settings.machineName[source_id].instance.attachUI($context, $source);
+          }
         }
-
-        eventData = {
-          $source: $source,
-          $target: $target,
-          $suffix: $suffix,
-          $wrapper: $wrapper,
-          $preview: $preview,
-          options: options
-        };
-        // If it is editable, append an edit link.
-        var $link = $('<span class="admin-link"><button type="button" class="link">' + Drupal.t('Edit') + '</button></span>').on('click', eventData, clickEditHandler);
-        $suffix.append($link);
-
-        // Preview the machine name in realtime when the human-readable name
-        // changes, but only if there is no machine name yet; i.e., only upon
-        // initial creation, not when editing.
-        if ($target.val() === '') {
-          $source.on('formUpdated.machineName', eventData, machineNameHandler)
-            // Initialize machine name preview.
-            .trigger('formUpdated.machineName');
-        }
-
-        // Add a listener for an invalid event on the machine name input
-        // to show its container and focus it.
-        $target.on('invalid', eventData, clickEditHandler);
       });
     },
-
-    showMachineName: function (machine, data) {
-      var settings = data.options;
-      // Set the machine name to the transliterated value.
-      if (machine !== '') {
-        if (machine !== settings.replace) {
-          data.$target.val(machine);
-          data.$preview.html(settings.field_prefix + Drupal.checkPlain(machine) + settings.field_suffix);
-        }
-        data.$suffix.show();
-      }
-      else {
-        data.$suffix.hide();
-        data.$target.val(machine);
-        data.$preview.empty();
-      }
-    },
-
     /**
-     * Transliterate a human-readable name to a machine name.
-     *
-     * @param {string} source
-     *   A string to transliterate.
-     * @param {object} settings
-     *   The machine name settings for the corresponding field.
-     * @param {string} settings.replace_pattern
-     *   A regular expression (without modifiers) matching disallowed characters
-     *   in the machine name; e.g., '[^a-z0-9]+'.
-     * @param {string} settings.replace_token
-     *   A token to validate the regular expression.
-     * @param {string} settings.replace
-     *   A character to replace disallowed characters with; e.g., '_' or '-'.
-     * @param {number} settings.maxlength
-     *   The maximum length of the machine name.
-     *
-     * @return {jQuery}
-     *   The transliterated source string.
+     * Detaches the behavior.
      */
-    transliterate: function (source, settings) {
-      return $.get(Drupal.url('machine_name/transliterate'), {
-        text: source,
-        langcode: drupalSettings.langcode,
-        replace_pattern: settings.replace_pattern,
-        replace_token: settings.replace_token,
-        replace: settings.replace,
-        lowercase: true
+    detach: function (context, settings) {
+      Object.keys(settings.machineName).forEach(function (source_id) {
+        if (settings.machineName[source_id].instance) {
+          settings.machineName[source_id].instance.detachUI();
+          delete settings.machineName[source_id].instance;
+        }
       });
     }
   };
