diff --git a/core/misc/tabledrag.js b/core/misc/tabledrag.js
index 230f588..2cf8f22 100644
--- a/core/misc/tabledrag.js
+++ b/core/misc/tabledrag.js
@@ -597,7 +597,7 @@ Drupal.tableDrag.prototype.dropRow = function (event, self) {
 
       self.rowObject.markChanged();
       if (self.changed === false) {
-        $(Drupal.theme('tableDragChangedWarning')).insertBefore(self.table).hide().fadeIn('slow');
+        self.rowObject.addChangedWarning();
         self.changed = true;
       }
     }
@@ -1222,6 +1222,10 @@ Drupal.tableDrag.prototype.row.prototype.markChanged = function () {
   }
 };
 
+Drupal.tableDrag.prototype.row.prototype.addChangedWarning = function () {
+  $(Drupal.theme('tableDragChangedWarning')).insertBefore(this.table).hide().fadeIn('slow');
+}
+
 /**
  * Stub function. Allows a custom handler when a row is indented.
  */
diff --git a/core/modules/field_ui/field_ui.admin.inc b/core/modules/field_ui/field_ui.admin.inc
index c51a309..34af1cd 100644
--- a/core/modules/field_ui/field_ui.admin.inc
+++ b/core/modules/field_ui/field_ui.admin.inc
@@ -223,6 +223,12 @@ function theme_field_ui_table($variables) {
   $elements = $variables['elements'];
   $table = array();
   $js_settings = array();
+  $output = '';
+
+  // 'Dirty' message.
+  if (!empty($elements['#dirty'])) {
+    $output .= theme('field_ui_changed_warning', array('message' => t('Changes made in this table will not be saved until the form is submitted.'), 'id' => 'field-ui-changed-warning'));
+  }
 
   // Add table headers and attributes.
   foreach (array('header', 'attributes') as $key) {
@@ -288,7 +294,27 @@ function theme_field_ui_table($variables) {
     }
   }
 
-  return theme('table', $table);
+  $output .= theme('table', $table);
+
+  return $output;
+}
+
+/**
+ * Returns HTML for a warning message above Field UI overview tables.
+ *
+ * @ingroup themeable
+ */
+function theme_field_ui_changed_warning($variables) {
+  return '<div id="' . check_plain($variables['id']) . '" class="messages warning"><span class="warning tabledrag-changed">*</span>&nbsp;' . check_plain($variables['message']) . '</div>';
+}
+
+/**
+ * Returns HTML for a changed row.
+ *
+ * @ingroup themeable
+ */
+function theme_field_ui_asterix($variables) {
+  return '<span class="warning tabledrag-changed">*</span>';
 }
 
 /**
diff --git a/core/modules/field_ui/field_ui.js b/core/modules/field_ui/field_ui.js
index d55f30b..2e54b8b 100644
--- a/core/modules/field_ui/field_ui.js
+++ b/core/modules/field_ui/field_ui.js
@@ -109,7 +109,7 @@ jQuery.fn.fieldUIPopulateOptions = function (options, selected) {
 
 Drupal.behaviors.fieldUIDisplayOverview = {
   attach: function (context, settings) {
-    $(context).find('table#field-display-overview').once('field-display-overview', function() {
+    $(context).find('table#field-display-overview').once('field-display-overview-form', function() {
       Drupal.fieldUIOverview.attach(this, settings.fieldUIRowsData, Drupal.fieldUIDisplayOverview);
     });
   }
@@ -126,6 +126,14 @@ Drupal.fieldUIOverview = {
     tableDrag.onDrop = this.onDrop;
     tableDrag.row.prototype.onSwap = this.onSwap;
 
+    // The AJAX behavior on formatter settings updates the whole table, so we
+    // make sure the 'Save' warning is displayed only once on the page.
+    tableDrag.row.prototype.addChangedWarning = function () {
+      if (!$('#field-ui-changed-warning').length) {
+        $(Drupal.theme('FieldUIChangedWarning')).prependTo('.field-overview-wrapper').hide().fadeIn('slow');
+      }
+    };
+
     // Create row handlers.
     $(table).find('tr.draggable').each(function () {
       // Extract server-side data for the row.
@@ -138,7 +146,7 @@ Drupal.fieldUIOverview = {
         var rowHandler = new rowHandlers[data.rowHandler](row, data);
         $(row).data('fieldUIRowHandler', rowHandler);
       }
-    });
+    });    
   },
 
   /**
@@ -166,6 +174,15 @@ Drupal.fieldUIOverview = {
     // Ajax-update the rows.
     Drupal.fieldUIOverview.AJAXRefreshRows(refreshRows);
   },
+  
+  /**
+   * Event handler to be attached to form selects triggering a label change.
+   */
+  onChangeLabel: function () {
+    if (!$('#field-ui-changed-warning').length) {
+      $(Drupal.theme('FieldUIChangedWarning')).prependTo('.field-overview-wrapper').hide().fadeIn('slow');
+    }
+  },
 
   /**
    * Lets row handlers react when a row is dropped into a new region.
@@ -176,6 +193,14 @@ Drupal.fieldUIOverview = {
     var $row = $(row);
     var rowHandler = $row.data('fieldUIRowHandler');
     if (typeof rowHandler !== 'undefined') {
+
+      // Allow server-side code to track dragged rows and preserve 'changed'
+      // markers and the warning message through AJAX refreshes of the table.
+      var $input = $('input[name=dragged_rows]');
+      var dragged = $input.val();
+      dragged += (dragged ? ' ' : '') + rowHandler.name;
+      $input.val(dragged);
+
       var regionRow = $row.prevAll('tr.region-message').get(0);
       var region = regionRow.className.replace(/([^ ]+[ ]+)*region-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
 
@@ -292,6 +317,10 @@ Drupal.fieldUIDisplayOverview.field = function (row, data) {
   this.$formatSelect = $(row).find('select.field-formatter-type');
   this.$formatSelect.change(Drupal.fieldUIOverview.onChange);
 
+  // Attach change listener to the 'label' select.
+  this.$labelSelect = $(row).find('select.field-label');
+  this.$labelSelect.change(Drupal.fieldUIOverview.onChangeLabel);
+
   return this;
 };
 
@@ -350,4 +379,10 @@ Drupal.fieldUIDisplayOverview.field.prototype = {
   }
 };
 
+$.extend(Drupal.theme, {
+  FieldUIChangedWarning: function () {
+    return '<div id="field-ui-changed-warning" class="messages warning">' + Drupal.theme('tableDragChangedMarker') + ' ' + Drupal.t('Changes made in this table will not be saved until the form is submitted.') + '</div>';
+  }
+});
+
 })(jQuery);
diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module
index a04c0dd..0f94c91 100644
--- a/core/modules/field_ui/field_ui.module
+++ b/core/modules/field_ui/field_ui.module
@@ -288,6 +288,14 @@ function field_ui_theme() {
     'field_ui_table' => array(
       'render element' => 'elements',
     ),
+    'field_ui_changed_warning' => array(
+      'variables' => array('message' => NULL, 'id' => NULL),
+      'file' => 'field_ui.admin.inc',
+    ),
+    'field_ui_asterix' => array(
+      'variables' => array(),
+      'file' => 'field_ui.admin.inc',
+    ),
   );
 }
 
diff --git a/core/modules/field_ui/lib/Drupal/field_ui/DisplayOverview.php b/core/modules/field_ui/lib/Drupal/field_ui/DisplayOverview.php
index a777f3e..2631f14 100644
--- a/core/modules/field_ui/lib/Drupal/field_ui/DisplayOverview.php
+++ b/core/modules/field_ui/lib/Drupal/field_ui/DisplayOverview.php
@@ -52,8 +52,15 @@ public function form(array $form, array &$form_state) {
 
     $form_state += array(
       'formatter_settings_edit' => NULL,
+      'dirty' => FALSE,
+      'dirty_rows' => array(),
     );
 
+    if (!empty($form_state['values']['dragged_rows'])) {
+      $form_state['dirty'] = TRUE;
+      $form_state['dirty_rows'] += drupal_map_assoc(explode(' ', $form_state['values']['dragged_rows']));
+    }
+
     $form += array(
       '#entity_type' => $this->entity_type,
       '#bundle' => $this->bundle,
@@ -67,6 +74,8 @@ public function form(array $form, array &$form_state) {
       return $form;
     }
 
+    $changed_asterisk = theme('field_ui_asterix');
+
     $table = array(
       '#type' => 'field_ui_table',
       '#tree' => TRUE,
@@ -84,8 +93,9 @@ public function form(array $form, array &$form_state) {
         'id' => 'field-display-overview',
       ),
       // Add Ajax wrapper.
-      '#prefix' => '<div id="field-display-overview-wrapper">',
+      '#prefix' => '<div id="field-display-overview-wrapper" class="field-overview-wrapper">',
       '#suffix' => '</div>',
+      '#dirty' => $form_state['dirty'],
     );
 
     $field_label_options = array(
@@ -100,6 +110,7 @@ public function form(array $form, array &$form_state) {
 
     // Field rows.
     foreach ($instances as $name => $instance) {
+      $changed_marker = !empty($form_state['dirty_rows'][$name]) ? $changed_asterisk : '';
       $field = field_info_field($instance['field_name']);
 
       if (isset($instance['display'][$this->view_mode])) {
@@ -122,7 +133,7 @@ public function form(array $form, array &$form_state) {
           'defaultFormatter' => $field_types[$field['type']]['default_formatter'],
         ),
         'human_name' => array(
-          '#markup' => check_plain($instance['label']),
+          '#markup' => check_plain($instance['label']) . $changed_marker,
         ),
         'weight' => array(
           '#type' => 'textfield',
@@ -154,6 +165,7 @@ public function form(array $form, array &$form_state) {
           '#title_display' => 'invisible',
           '#options' => $field_label_options,
           '#default_value' => $display['label'],
+          '#attributes' => array('class' => array('field-label')),
         ),
       );
 
@@ -237,7 +249,7 @@ public function form(array $form, array &$form_state) {
                   '#op' => 'cancel',
                   // Do not check errors for the 'Cancel' button, but make sure we
                   // get the value of the 'formatter type' select.
-                  '#limit_validation_errors' => array(array('fields', $name, 'type')),
+                  '#limit_validation_errors' => array(array('fields', $name, 'type'), array('dragged_rows')),
                 ),
               ),
             );
@@ -276,7 +288,7 @@ public function form(array $form, array &$form_state) {
               '#op' => 'edit',
               // Do not check errors for the 'Edit' button, but make sure we get
               // the value of the 'formatter type' select.
-              '#limit_validation_errors' => array(array('fields', $name, 'type')),
+              '#limit_validation_errors' => array(array('fields', $name, 'type'), array('dragged_rows')),
               '#prefix' => '<div class="field-formatter-settings-edit-wrapper">',
               '#suffix' => '</div>',
             );
@@ -288,13 +300,14 @@ public function form(array $form, array &$form_state) {
     // Non-field elements.
     foreach ($extra_fields as $name => $extra_field) {
       $display = $extra_field['display'][$this->view_mode];
+      $changed_marker = !empty($form_state['dirty_rows'][$name]) ? $changed_asterisk : '';
       $table[$name] = array(
         '#attributes' => array('class' => array('draggable', 'tabledrag-leaf')),
         '#row_type' => 'extra_field',
         '#region_callback' => 'field_ui_display_overview_row_region',
         '#js_settings' => array('rowHandler' => 'field'),
         'human_name' => array(
-          '#markup' => check_plain($extra_field['label']),
+          '#markup' => check_plain($extra_field['label']) . $changed_marker,
         ),
         'weight' => array(
           '#type' => 'textfield',
@@ -504,10 +517,13 @@ public function multistepSubmit($form, &$form_state) {
         break;
 
       case 'update':
-        // Store the saved settings, and set the field back to 'non edit' mode.
+        // Store the saved settings, mark the row as 'dirty', and set it back to
+        // 'non edit' mode.
         $field_name = $trigger['#field_name'];
         $values = $form_state['values']['fields'][$field_name]['settings_edit_form']['settings'];
         $form_state['formatter_settings'][$field_name] = $values;
+        $form_state['dirty'] = TRUE;
+        $form_state['dirty_rows'][$field_name] = $field_name;
         unset($form_state['formatter_settings_edit']);
         break;
 
@@ -517,12 +533,15 @@ public function multistepSubmit($form, &$form_state) {
         break;
 
       case 'refresh_table':
+        $updated_rows = explode(' ', $form_state['values']['refresh_rows']);
         // If the currently edited field is one of the rows to be refreshed, set
         // it back to 'non edit' mode.
-        $updated_rows = explode(' ', $form_state['values']['refresh_rows']);
         if (isset($form_state['formatter_settings_edit']) && in_array($form_state['formatter_settings_edit'], $updated_rows)) {
           unset($form_state['formatter_settings_edit']);
         }
+        // Mark rows as dirty.
+        $form_state['dirty'] = TRUE;
+        $form_state['dirty_rows'] += drupal_map_assoc($updated_rows);
         break;
     }
 
diff --git a/core/modules/field_ui/lib/Drupal/field_ui/FieldOverview.php b/core/modules/field_ui/lib/Drupal/field_ui/FieldOverview.php
index 177e3fd..a686f4d 100644
--- a/core/modules/field_ui/lib/Drupal/field_ui/FieldOverview.php
+++ b/core/modules/field_ui/lib/Drupal/field_ui/FieldOverview.php
@@ -391,7 +391,7 @@ public function form(array $form, array &$form_state) {
     $form['fields'] = $table;
 
     // Add AJAX wrapper.
-    $form['fields']['#prefix'] = '<div id="field-display-overview-wrapper">';
+    $form['fields']['#prefix'] = '<div id="field-display-overview-wrapper" class="field-overview-wrapper">';
     $form['fields']['#suffix'] = '</div>';
 
     // This key is used to store the current updated field.
