diff --git a/field_remove_item.info b/field_remove_item.info
index 90cc25e..4ea7b89 100644
--- a/field_remove_item.info
+++ b/field_remove_item.info
@@ -1,4 +1,4 @@
 name = Field Remove Item
-description = Adds a remove option on each form element of unlimited value fields.
+description = Adds a remove button to each item of fields with unlimited cardinality.
 package = Fields
 core = 7.x
diff --git a/field_remove_item.module b/field_remove_item.module
index 1aadff0..500f0fd 100644
--- a/field_remove_item.module
+++ b/field_remove_item.module
@@ -1,118 +1,182 @@
 <?php
 /**
  * @file
- * Adds a checkbox remove option on each item of all multivalue fields.
+ * Adds a remove button to each item of fields with unlimited cardinality.
  */
 
 /**
+ * Implements hook_menu().
+ */
+function field_remove_item_menu() {
+  $items = array();
+
+  $items['field_remove_item/ajax'] = array(
+    'title' => 'Remove item callback',
+    'page callback' => 'field_remove_item_js',
+    'delivery callback' => 'ajax_deliver',
+    'access callback' => TRUE,
+    'theme callback' => 'ajax_base_page_theme',
+    'type' => MENU_CALLBACK,
+    'file path' => 'includes',
+    'file' => 'form.inc',
+  );
+
+  return $items;
+}
+
+/**
  * Implements hook_field_widget_form_alter().
  *
- * Adds a remove option into each field item on all multivalue fields.
+ * Adds a remove button to each item of fields with unlimited cardinality.
  *
- * Removes the field item via ajax when that particular field item remove option
- * is triggered.
+ * @see field_multiple_value_form()
  */
 function field_remove_item_field_widget_form_alter(&$element, &$form_state, $context) {
-  // Every instance of a multivalue field got the _weight element.
-  if (isset($element['_weight'])) {
-
-    if (!isset($form_state['#field_remove_item'])) {
-      $form_state['#field_remove_item'] = array();
-    }
+  // Let's have some useful variables.
+  extract($context);
+  
+  // Modules such as field_collection add their own "Remove" button.
+  if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED && empty($form_state['programmed']) && !isset($element['remove_button'])) {
+    $field_name = $field['field_name'];
+    $parents = array_merge($form['#parents'], array($field_name, $langcode, $delta));
 
-    // Add ajax trigger.
-    $element['remove_option'] = array(
-      '#type' => 'checkbox',
-      '#prefix' => t('Remove'),
+    // Unfortunately, field_multiple_value_form() sets the wrapper html id after
+    // hook_field_widget_form_alter() has been invoked, so we can't guess the id
+    // here, that's why we do not use a wrapper, but a path instead.
+    // Thank you, field_collection.
+    $element['remove_item'] = array(
+      '#delta' => $delta,
+      '#name' => implode('_', $parents) . '_remove_item',
+      '#type' => 'submit',
+      '#value' => t('Remove'),
+      '#validate' => array(),
+      '#submit' => array('field_remove_item_submit'),
+      '#limit_validation_errors' => array(),
       '#ajax' => array(
-        'callback' => 'field_remove_item_ajax_callback',
+        'path' => 'field_remove_item/ajax',
       ),
+      '#weight' => 1000,
     );
-
-    // Set default remove option to FALSE (keep element).
-    $delta_values = array(
-      'remove_option' => FALSE,
-    );
-    // Check if the current element has been submitted.
-    if (isset($form_state['input'][$element['#field_name']][$element['#language']][$element['#delta']])) {
-      $delta_values = $form_state['input'][$element['#field_name']][$element['#language']][$element['#delta']];
-    }
-
-    // Check if user has removed the current element.
-    if ($delta_values['remove_option']) {
-      // Loop through element values.
-      foreach ($delta_values as $delta_column => $delta_column_value) {
-        // Keep value of remove_option (in this case TRUE).
-        if ($delta_column != 'remove_option') {
-          // Empty default values.
-          $element[$delta_column]['#default_value'] = '';
-        }
-      }
-
-      if (!isset($form_state['#field_remove_item'][$element['#field_name']])) {
-        // This is the first item of this field being removed.
-        $form_state['#field_remove_item'][$element['#field_name']] = array(
-          '#language' => $element['#language'],
-          '#deltas' => array($element['#delta']),
-        );
-      }
-      else {
-        $form_state['#field_remove_item'][$element['#field_name']]['#deltas'][] = $element['#delta'];
-      }
-    }
   }
 }
 
 /**
- * Ajax callback.
+ * Submit handler for the "Remove" button of a field form.
+ *
+ * @see field_add_more_submit()
  */
-function field_remove_item_ajax_callback(&$form, $form_state) {
-  // Find out which element triggered this callback.
-  $trigger_element = str_replace('][', '|', $form_state['input']['_triggering_element_name']);
-  $trigger_element = str_replace('[', '|', $trigger_element);
-  $trigger_element = str_replace(']', '', $trigger_element);
+function field_remove_item_submit($form, &$form_state) {
+  $button = $form_state['triggering_element'];
+  $delta = $button['#delta'];
 
-  $trigger_element = explode('|', $trigger_element);
+  // Where in the form we'll find the parent element.
+  $address = array_slice($button['#array_parents'], 0, -2);
 
-  // Field parts.
-  $field_name = $trigger_element[0];
-  $field_language = $trigger_element[1];
-  $field_delta = $trigger_element[2];
+  // Go one level up in the form, to the widgets container.
+  $parent_element = drupal_array_get_nested_value($form, $address);
+  $field_name = $parent_element['#field_name'];
+  $langcode = $parent_element['#language'];
+  $parents = $parent_element['#field_parents'];
 
-  // Convert the field name into the field css class name.
-  $wrapper = str_replace('_', '-', $field_name);
+  $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state);
+  // Go ahead and renumber everything from our delta to the last
+  // item down one. This will overwrite the item being removed.
+  for ($i = $delta; $i < $field_state['items_count']; $i++) {
+    $old_element_address = array_merge($address, array($i + 1));
+    $new_element_address = array_merge($address, array($i));
 
-  // Remove field from the form.
-  unset($form[$field_name][$field_language][$field_delta]);
+    $moving_element = drupal_array_get_nested_value($form, $old_element_address);
+    $moving_element_value = drupal_array_get_nested_value($form_state['values'], $old_element_address);
+    $moving_element_input = drupal_array_get_nested_value($form_state['input'], $old_element_address);
 
-  return array(
-    '#type' => 'ajax',
-    '#commands' => array(
-      ajax_command_replace(".field-name-$wrapper", render($form[$field_name])),
-    ),
-  );
+    // Tell the element where it's being moved to.
+    $moving_element['#parents'] = $new_element_address;
+
+    // Move the element around.
+    form_set_value($moving_element, $moving_element_value, $form_state);
+    drupal_array_set_nested_value($form_state['input'], $moving_element['#parents'], $moving_element_input);
+
+    // Move the entity in our saved state.
+    $field_state['entity'][$i] = $field_state['entity'][$i + 1];
+  }
+
+  // For the final item there is nothing to move into its place, so remove it.
+  $i = $field_state['items_count'];
+  $removed_element_address = array_merge($address, array($i));
+  $removed_element = drupal_array_get_nested_value($form, $removed_element_address);
+  form_set_value($removed_element, NULL, $form_state);
+  drupal_array_set_nested_value($form_state['input'], $removed_element['#parents'], NULL);
+
+  // Then remove the last item. But we must not go negative.
+  if ($field_state['items_count'] > 0) {
+    $field_state['items_count']--;
+  }
+
+  // Fix the weights. Field UI lets the weights be in a range of
+  // (-1 * item_count) to (item_count). This means that when we remove one,
+  // the range shrinks; weights outside of that range then get set to
+  // the first item in the select by the browser, floating them to the top.
+  // We use a brute force method because we lost weights on both ends
+  // and if the user has moved things around, we have to cascade because
+  // if I have items weight weights 3 and 4, and I change 4 to 3 but leave
+  // the 3, the order of the two 3s now is undefined and may not match what
+  // the user had selected.
+  $input = drupal_array_get_nested_value($form_state['input'], $address);
+  // Sort by weight
+  uasort($input, '_field_sort_items_helper');
+
+  // Reweight everything in the correct order.
+  $weight = -1 * $field_state['items_count'];
+  foreach ($input as $key => $item) {
+    if ($item) {
+      $input[$key]['_weight'] = $weight++;
+    }
+  }
+
+  drupal_array_set_nested_value($form_state['input'], $address, $input);
+
+  field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state);
+
+  $form_state['rebuild'] = TRUE;
 }
 
 /**
- * Implements hook_form_alter().
+ * Ajax callback for the "Remove" button of a field form.
  *
- * In case the form fails any sort of validation upon submission after the user
- * had removed one or more field items, those removed element items are some
- * how rebuilt into the form.
+ * Stolen from field_collection.
  *
- * To overcome that this hook implementation will remove again all the
- * previously removed items.
+ * @see field_add_more_js()
  */
-function field_remove_item_form_alter(&$form, &$form_state, $form_id) {
-  if (isset($form_state['#field_remove_item'])) {
-    // Form has field items that was removed by the user.
-    foreach ($form_state['#field_remove_item'] as $field_name => $item_attributes) {
-      foreach ($item_attributes['#deltas'] as $delta) {
-        if (isset($form[$field_name][$item_attributes['#language']][$delta])) {
-          // Remove it again.
-          unset($form[$field_name][$item_attributes['#language']][$delta]);
-        }
-      }
-    }
+function field_remove_item_js() { //$form, $form_state) {
+  // drupal_html_id() very helpfully ensures that all html IDS are unique
+  // on a page. Unfortunately what it doesn't realize is that the IDs
+  // we are generating are going to replace IDs that already exist, so
+  // this actually works against us.
+  if (isset($_POST['ajax_html_ids'])) {
+    unset($_POST['ajax_html_ids']);
+  }
+
+  list($form, $form_state) = ajax_get_form();
+  drupal_process_form($form['#form_id'], $form, $form_state);
+
+  // Get the information on what we're removing.
+  $button = $form_state['triggering_element'];
+  // Go two levels up in the form, to the whole widget.
+  $element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -3));
+  // Now send back the proper AJAX command to replace it.
+  $return = array(
+    '#type' => 'ajax',
+    '#commands' => array(
+      ajax_command_replace('#' . $element['#id'], drupal_render($element)),
+    ),
+  );
+
+  // Because we're doing this ourselves, messages aren't automatic. We have
+  // to add them.
+  $messages = theme('status_messages');
+  if ($messages) {
+    $return['#commands'][] = ajax_command_prepend('#' . $element['#id'], $messages);
   }
+
+  return $return;
 }
