diff --git modules/field_ui/field_ui.js modules/field_ui/field_ui.js
index 574c6a1..e777c2e 100644
--- modules/field_ui/field_ui.js
+++ modules/field_ui/field_ui.js
@@ -174,7 +174,6 @@ Drupal.fieldUIOverview = {
     var rowHandler = $(row).data('fieldUIRowHandler');
 
     // @todo do we want it here ?
-    // @todo check consistency with 'refresh_rows'
     // Allow the server to track dragged rows and preserve 'changed' markers
     // and the warning message through AJAX refreshes of the table.
     var $input = $('input[name=dragged_rows]');
diff --git misc/tabledrag.js misc/tabledrag.js
index 867f9c3..9c51752 100644
--- misc/tabledrag.js
+++ misc/tabledrag.js
@@ -539,7 +539,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;
       }
     }
@@ -1147,6 +1147,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 modules/field_ui/field_ui.admin.inc modules/field_ui/field_ui.admin.inc
index 6274d5a..617228b 100644
--- modules/field_ui/field_ui.admin.inc
+++ modules/field_ui/field_ui.admin.inc
@@ -196,6 +196,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) {
@@ -256,7 +262,20 @@ 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) {
+  //'<div class="tabledrag-changed-warning messages warning">' + Drupal.theme('tableDragChangedMarker') + ' ' + Drupal.t('Changes made in this table will not be saved until the form is submitted.') + '</div>';
+  // @todo sanitize id ?
+  return '<div id="' . $variables['id'] . '" class="messages warning"><span class="warning tabledrag-changed">*</span>&nbsp;' . check_plain($variables['message']) . '</div>';
 }
 
 /**
@@ -841,8 +860,15 @@ function field_ui_display_overview_form($form, &$form_state, $entity_type, $bund
 
   $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' => $entity_type,
     '#bundle' => $bundle,
@@ -856,6 +882,9 @@ function field_ui_display_overview_form($form, &$form_state, $entity_type, $bund
     return $form;
   }
 
+  // @todo theme function
+  $changed_asterisk = '<span class="warning tabledrag-changed">*</span>';
+
   $table = array(
     '#type' => 'field_ui_table',
     '#tree' => TRUE,
@@ -878,6 +907,7 @@ function field_ui_display_overview_form($form, &$form_state, $entity_type, $bund
     // Add AJAX wrapper.
     '#prefix' => '<div id="field-display-overview-wrapper">',
     '#suffix' => '</div>',
+    '#dirty' => $form_state['dirty'],
   );
 
   $field_label_options = array(
@@ -894,6 +924,7 @@ function field_ui_display_overview_form($form, &$form_state, $entity_type, $bund
   foreach ($instances as $name => $instance) {
     $field = field_info_field($instance['field_name']);
     $display = $instance['display'][$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',
@@ -903,7 +934,7 @@ function field_ui_display_overview_form($form, &$form_state, $entity_type, $bund
         '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',
@@ -1023,7 +1054,7 @@ function field_ui_display_overview_form($form, &$form_state, $entity_type, $bund
               '#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')),
             ),
           ),
         );
@@ -1048,7 +1079,7 @@ function field_ui_display_overview_form($form, &$form_state, $entity_type, $bund
           '#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>',
         );
@@ -1059,13 +1090,14 @@ function field_ui_display_overview_form($form, &$form_state, $entity_type, $bund
   // Non-field elements.
   foreach ($extra_fields as $name => $extra_field) {
     $display = $extra_field['display'][$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',
@@ -1163,6 +1195,10 @@ function field_ui_display_overview_form($form, &$form_state, $entity_type, $bund
     ),
   );
 
+  // @todo comment
+  // 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'));
 
@@ -1192,10 +1228,13 @@ function field_ui_display_overview_multistep_submit($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;
 
@@ -1205,12 +1244,15 @@ function field_ui_display_overview_multistep_submit($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 modules/field_ui/field_ui.js modules/field_ui/field_ui.js
index cd27bff..574c6a1 100644
--- modules/field_ui/field_ui.js
+++ modules/field_ui/field_ui.js
@@ -110,6 +110,20 @@ Drupal.fieldUIOverview = {
     tableDrag.onDrop = this.onDrop;
     tableDrag.row.prototype.onSwap = this.onSwap;
 
+    // @todo : do we want this here or in tabledrag's own method ?
+    // 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.
     $('tr.draggable', table).each(function () {
       // Extract server-side data for the row.
@@ -158,6 +172,17 @@ Drupal.fieldUIOverview = {
     var dragObject = this;
     var row = dragObject.rowObject.element;
     var rowHandler = $(row).data('fieldUIRowHandler');
+
+    // @todo do we want it here ?
+    // @todo check consistency with 'refresh_rows'
+    // Allow the server 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;
+//    dragged += (dragged ? ' ' : '') + row.id.replace('-', '_');
+    $input.val(dragged);
+
     if (rowHandler !== undefined) {
       var regionRow = $(row).prevAll('tr.region-message').get(0);
       var region = regionRow.className.replace(/([^ ]+[ ]+)*region-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
diff --git modules/field_ui/field_ui.module modules/field_ui/field_ui.module
index 3d2ddf6..4833fb7 100644
--- modules/field_ui/field_ui.module
+++ modules/field_ui/field_ui.module
@@ -277,6 +277,10 @@ 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',
+    ),
   );
 }
 
