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 1c29e13..d5e7060 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-settings-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 'Manage Display' 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..97ca470 100644
--- a/core/modules/field_ui/field_ui.js
+++ b/core/modules/field_ui/field_ui.js
@@ -126,6 +126,20 @@ 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-settings-changed-warning').length) {
+        $(Drupal.theme('tableDragChangedWarning')).insertBefore(this.table).hide().fadeIn('slow');
+      }
+    };
+    
+    // Custom message with an HTML id so that addChangedWarning() can avoid
+    // duplicates.
+    Drupal.theme.prototype.tableDragChangedWarning = function () {
+      return '<div id="field-settings-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>';
+    };          
+    
     // Create row handlers.
     $(table).find('tr.draggable').each(function () {
       // Extract server-side data for the row.
@@ -176,6 +190,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');
 
@@ -186,7 +208,7 @@ Drupal.fieldUIOverview = {
         rowHandler.region = region;
         // Ajax-update the rows.
         Drupal.fieldUIOverview.AJAXRefreshRows(refreshRows);
-      }
+      }      
     }
   },
 
diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module
index b3dc6f0..22c1e30 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 086f6ee..305a7df 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,
@@ -86,6 +95,7 @@ public function form(array $form, array &$form_state) {
       // Add Ajax wrapper.
       '#prefix' => '<div id="field-display-overview-wrapper">',
       '#suffix' => '</div>',
+      '#dirty' => $form_state['dirty'],
     );
 
     $field_label_options = array(
@@ -102,6 +112,7 @@ public function form(array $form, array &$form_state) {
     foreach ($instances as $name => $instance) {
       $field = field_info_field($instance['field_name']);
       $display = $instance['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' => 'field',
@@ -111,7 +122,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',
@@ -226,7 +237,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')),
                 ),
               ),
             );
@@ -265,7 +276,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>',
             );
@@ -277,13 +288,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',
@@ -384,6 +396,9 @@ public function form(array $form, array &$form_state) {
       ),
     );
 
+    // Hidden element used by field_ui.js to track dragged rows.
+    $form['dragged_rows'] = array('#type' => 'hidden');
+
     $form['actions'] = array('#type' => 'actions');
     $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save'));
 
@@ -492,10 +507,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;
 
@@ -505,12 +523,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;
     }
 
