diff --git a/elements.module b/elements.module
index 025c565..3c00dad 100644
--- a/elements.module
+++ b/elements.module
@@ -61,6 +61,19 @@ function elements_elements() {
   $types['textarea'] = array(
     '#process' => array('form_process_placeholder'),
   );
+  $types['machine_name'] = array(
+    '#input' => TRUE,
+    '#default_value' => NULL,
+    '#required' => TRUE,
+    '#maxlength' => 64,
+    '#size' => 60,
+    '#autocomplete_path' => FALSE,
+    '#process' => array('form_process_machine_name', 'ajax_process_form'),
+    '#element_validate' => array('form_validate_machine_name'),
+    '#theme' => 'textfield',
+    '#theme_wrappers' => array('form_element'),
+  );
+
   return $types;
 }
 
@@ -241,3 +254,119 @@ function form_process_tableselect($element) {
   }
   return $element;
 }
+/**
+ * Processes a machine-readable name form element.
+ *
+ * Backport of d7's form_process_machine_name
+ */
+function form_process_machine_name($element, $edit, $form_state, &$complete_form) {
+  // Apply default form element properties.
+  $element += array(
+    '#title' => t('Machine-readable name'),
+    '#description' => t('A unique machine-readable name. Can only contain lowercase letters, numbers, and underscores.'),
+    '#machine_name' => array(
+      'exists' => 'isset',
+    ),
+  );
+  // This is needed so it is themed correctly.
+  $element['#type'] = 'textfield';
+  // A form element that only wants to set one #machine_name property (usually
+  // 'source' only) would leave all other properties undefined, if the defaults
+  // were defined in hook_elements(). Therefore, we apply the defaults here.
+  $element['#machine_name'] += array(
+    'source' => array('name'),
+    'target' => '#' . $element['#id'],
+    'label' => t('Machine name'),
+    'replace_pattern' => '[^a-z0-9_]+',
+    'replace' => '_',
+  );
+
+  // The source element defaults to array('name'), but may have been overidden.
+  if (empty($element['#machine_name']['source'])) {
+    return $element;
+  }
+
+  // Retrieve the form element containing the human-readable name from the
+  // complete form in $form_state. By reference, because we need to append
+  // a #field_suffix that will hold the live preview.
+  $key_exists = NULL;
+  $source = elements_array_get_nested_value($complete_form, $element['#machine_name']['source'], $key_exists);
+  if (!$key_exists) {
+    return $element;
+  }
+
+  // Append a field suffix to the source form element, which will contain
+  // the live preview of the machine name.
+  $suffix_id = $source['#id'] . '-machine-name-suffix';
+
+  $element['#machine_name']['suffix'] = $suffix_id;
+
+  $js_settings = array(
+    'elementsMachineName' => array(
+      '#' . $source['#id'] => $element['#machine_name'],
+    ),
+  );
+
+  drupal_add_js(drupal_get_path('module', 'elements') . '/js/machine-name.js');
+  drupal_add_js($js_settings, 'setting');
+
+  return $element;
+}
+
+/**
+ * Form element validation handler for #type 'machine_name'.
+ *
+ * Note that #maxlength is validated by _form_validate() already.
+ *
+ * Backport of d7's form_validate_machine_name
+ */
+function form_validate_machine_name(&$element, &$form_state) {
+  // Verify that the machine name not only consists of replacement tokens.
+  if (preg_match('@^' . $element['#machine_name']['replace'] . '+$@', $element['#value'])) {
+    form_error($element, t('The machine-readable name must contain unique characters.'));
+  }
+
+  // Verify that the machine name contains no disallowed characters.
+  if (preg_match('@' . $element['#machine_name']['replace_pattern'] . '@', $element['#value'])) {
+    if (!isset($element['#machine_name']['error'])) {
+      // Since a hyphen is the most common alternative replacement character,
+      // a corresponding validation error message is supported here.
+      if ($element['#machine_name']['replace'] == '-') {
+        form_error($element, t('The machine-readable name must contain only lowercase letters, numbers, and hyphens.'));
+      }
+      // Otherwise, we assume the default (underscore).
+      else {
+        form_error($element, t('The machine-readable name must contain only lowercase letters, numbers, and underscores.'));
+      }
+    }
+    else {
+      form_error($element, $element['#machine_name']['error']);
+    }
+  }
+
+  // Verify that the machine name is unique.
+  if ($element['#default_value'] !== $element['#value']) {
+    $function = $element['#machine_name']['exists'];
+    if ($function($element['#value'], $element, $form_state)) {
+      form_error($element, t('The machine-readable name is already in use. It must be unique.'));
+    }
+  }
+}
+
+/**
+ * Retrieves a value from a nested array with variable depth.
+ *
+ * Backport of d7's drupal_array_get_nested_value
+ */
+function elements_array_get_nested_value(array &$array, array $parents, &$key_exists = NULL) {
+  $ref = &$array;
+  foreach ($parents as $parent) {
+    if (is_array($ref) && array_key_exists($parent, $ref)) {
+      $ref = &$ref[$parent];
+    }
+    else {
+      $key_exists = FALSE;
+      return NULL;
+    }
+  }
+  $key_exists = TRUE;
+  return $ref;
+}
diff --git a/js/machine-name.js b/js/machine-name.js
new file mode 100644
index 0000000..9098a08
--- /dev/null
+++ b/js/machine-name.js
@@ -0,0 +1,103 @@
+(function ($) {
+
+/**
+ * Attach the machine-readable name form element behavior.
+ */
+Drupal.behaviors.machineName = function(context) {
+  /**
+   * Drupal.settings.elementsMachineName
+   *   A list of elements to process, keyed by the HTML ID of the form element
+   *   containing the human-readable value. Each element is an object defining
+   *   the following properties:
+   *   - target: The HTML ID of the machine name form element.
+   *   - suffix: The HTML ID of a container to show the machine name preview in
+   *     (usually a field suffix after the human-readable name form element).
+   *   - label: The label to show for the machine name preview.
+   *   - 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 '-'.
+   */
+  $.each(Drupal.settings.elementsMachineName, function (source_id, options) {
+    var $source = $(source_id, context).addClass('machine-name-source');
+    var $target = $(options.target, context).addClass('machine-name-target');
+    var $wrapper = $target.parents('.form-item:first');
+    // All elements have to exist.
+    if (!$source.length || !$target.length || !$wrapper.length) {
+      return;
+    }
+    // Can't edit other elements in D6 process functions unlike D7
+    // so make up for it here
+
+    // Add the suffix
+    $source.after('<span class="field-suffix"><small id="' + options.suffix + '">&nbsp;</small></span>');
+    var $suffix = $('#' + options.suffix, context);
+
+    // Skip processing upon a form validation error on the machine name.
+    if ($target.hasClass('error')) {
+      return;
+    }
+    // Hide the form item container of the machine name form element.
+    $wrapper.hide();
+    // 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() != '') {
+      var machine = $target.val();
+    }
+    else {
+      var machine = elements_machine_name_transliterate($source.val(), options);
+    }
+
+    // Append the machine name preview to the source field.
+    var $preview = $('<span class="machine-name-value">' + machine + '</span>');
+    $suffix.empty()
+      .append(' ').append('<span class="machine-name-label">' + options.label + ':</span>')
+      .append(' ').append($preview);
+
+    // If the machine name cannot be edited, stop further processing.
+    if ($target.is(':disabled')) {
+      return;
+    }
+
+    // If it is editable, append an edit link.
+    var $link = $('<span class="admin-link"><a href="#">' + Drupal.t('Edit') + '</a></span>')
+      .click(function () {
+        $wrapper.show();
+        $target.focus();
+        $suffix.hide();
+        $source.unbind('.machineName');
+        return false;
+      });
+    $suffix.append(' ').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.bind('keyup.machineName change.machineName', function () {
+        machine = elements_machine_name_transliterate($(this).val(), options);
+        // Set the machine name to the transliterated value.
+        if (machine != options.replace && machine != '') {
+          $target.val(machine);
+          $preview.text(machine);
+          $suffix.show();
+        }
+        else {
+          $suffix.hide();
+          $target.val(machine);
+          $preview.empty();
+        }
+      });
+      // Initialize machine name preview.
+      $source.keyup();
+    }
+  });
+};
+
+function elements_machine_name_transliterate(source, settings) {
+  var rx = new RegExp(settings.replace_pattern, 'g');
+  return source.toLowerCase().replace(rx, settings.replace);
+}
+
+})(jQuery);
