diff --git a/core/modules/views_ui/src/ViewEditForm.php b/core/modules/views_ui/src/ViewEditForm.php
index 690973e7485d7be07a79c5ca98cf8a19e4927914..8ca5612dbbb9873d9ae760a540d944dc25e46dc3 100644
--- a/core/modules/views_ui/src/ViewEditForm.php
+++ b/core/modules/views_ui/src/ViewEditForm.php
@@ -361,8 +361,16 @@ public function getDisplayTab($view) {
       $build['details'] = $this->getDisplayDetails($view, $display->display);
     }
     // In AJAX context, ViewUI::rebuildCurrentTab() returns this outside of form
-    // context, so hook_form_views_ui_edit_form_alter() is insufficient.
+    // context, so hook_form_view_edit_form_alter() is insufficient.
+    // @todo remove this after
+    //   https://www.drupal.org/project/drupal/issues/3087455 has been resolved.
     \Drupal::moduleHandler()->alter('views_ui_display_tab', $build, $view, $display_id);
+    // Because themes can implement hook_form_FORM_ID_alter() and because this
+    // is a workaround for hook_form_view_edit_form_alter() being insufficient,
+    // also invoke this on themes.
+    // @todo remove this after
+    //   https://www.drupal.org/project/drupal/issues/3087455 has been resolved.
+    \Drupal::theme()->alter('views_ui_display_tab', $build, $view, $display_id);
     return $build;
   }
 
@@ -776,6 +784,18 @@ public function renderDisplayTop(ViewUI $view) {
       ];
     }
 
+    // In AJAX context, ViewUI::rebuildCurrentTab() returns this outside of form
+    // context, so hook_form_view_edit_form_alter() is insufficient.
+    // @todo remove this after
+    //   https://www.drupal.org/project/drupal/issues/3087455 has been resolved.
+    \Drupal::moduleHandler()->alter('views_ui_display_top', $element, $view, $display_id);
+    // Because themes can implement hook_form_FORM_ID_alter() and because this
+    // is a workaround for hook_form_view_edit_form_alter() being insufficient,
+    // also invoke this on themes.
+    // @todo remove this after
+    //   https://www.drupal.org/project/drupal/issues/3087455 has been resolved.
+    \Drupal::theme()->alter('views_ui_display_top', $element, $view, $display_id);
+
     return $element;
   }
 
diff --git a/core/modules/views_ui/tests/src/FunctionalJavascript/DisplayTest.php b/core/modules/views_ui/tests/src/FunctionalJavascript/DisplayTest.php
index 5444eb93bc873ad12aac5cd81f70a4f9380ae7a3..6b7cd1226f88b7fb0215af123a57f27448a9b822 100644
--- a/core/modules/views_ui/tests/src/FunctionalJavascript/DisplayTest.php
+++ b/core/modules/views_ui/tests/src/FunctionalJavascript/DisplayTest.php
@@ -122,4 +122,29 @@ protected function toggleContextualTriggerVisibility($selector) {
     $this->getSession()->executeScript("jQuery('{$selector} .contextual .trigger').toggleClass('visually-hidden');");
   }
 
+  /**
+   * Confirms that form_alter is triggered after ajax rebuilds.
+   */
+  public function testAjaxRebuild() {
+    \Drupal::service('theme_installer')->install(['views_test_classy_subtheme']);
+
+    $this->config('system.theme')
+      ->set('default', 'views_test_classy_subtheme')
+      ->save();
+
+    $page = $this->getSession()->getPage();
+    $assert_session = $this->assertSession();
+
+    $this->drupalGet('admin/structure/views/view/content');
+    $assert_session->pageTextContains('This is text added to the display tabs at the top');
+    $assert_session->pageTextContains('This is text added to the display edit form');
+    $page->clickLink('Content: Title (Title)');
+    $assert_session->waitForElementVisible('css', '.views-ui-dialog');
+    $page->fillField('Label', 'New Title');
+    $page->find('css', '.ui-dialog-buttonset button:contains("Apply")')->press();
+    $assert_session->waitForElementRemoved('css', '.views-ui-dialog');
+    $assert_session->pageTextContains('This is text added to the display tabs at the top');
+    $assert_session->pageTextContains('This is text added to the display edit form');
+  }
+
 }
diff --git a/core/modules/views_ui/tests/themes/views_test_classy_subtheme/views_test_classy_subtheme.info.yml b/core/modules/views_ui/tests/themes/views_test_classy_subtheme/views_test_classy_subtheme.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fbac46814d9f981478dd084e28d5a9c5e9057c07
--- /dev/null
+++ b/core/modules/views_ui/tests/themes/views_test_classy_subtheme/views_test_classy_subtheme.info.yml
@@ -0,0 +1,6 @@
+name: 'Theme test subtheme'
+type: theme
+description: 'Test theme which uses test_basetheme as the base theme.'
+version: VERSION
+core: 8.x
+base theme: classy
diff --git a/core/modules/views_ui/tests/themes/views_test_classy_subtheme/views_test_classy_subtheme.theme b/core/modules/views_ui/tests/themes/views_test_classy_subtheme/views_test_classy_subtheme.theme
new file mode 100644
index 0000000000000000000000000000000000000000..5e28fe1d241d131395fee70f4410ef7498b443df
--- /dev/null
+++ b/core/modules/views_ui/tests/themes/views_test_classy_subtheme/views_test_classy_subtheme.theme
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @file
+ * Implements Views UI alter hooks in a theme to provide test coverage.
+ */
+
+use Drupal\views_ui\ViewUI;
+
+/**
+ * Implements hook_views_ui_display_tab_alter().
+ */
+function views_test_classy_subtheme_views_ui_display_tab_alter(&$build, ViewUI $view, $display_id) {
+  $build['details']['top']['display_title']['#description'] = 'This is text added to the display edit form';
+}
+
+/**
+ * Implements hook_views_ui_display_top_alter().
+ */
+function views_test_classy_subtheme_views_ui_display_top_alter(&$build, ViewUI $view, $display_id) {
+  $build['tabs']['#suffix'] .= 'This is text added to the display tabs at the top';
+}
diff --git a/core/modules/views_ui/views_ui.api.php b/core/modules/views_ui/views_ui.api.php
new file mode 100644
index 0000000000000000000000000000000000000000..b220fb2cc2d9eecf05250e93c834d33649cdf544
--- /dev/null
+++ b/core/modules/views_ui/views_ui.api.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * @file
+ * Describes hooks provided by the Views UI module.
+ */
+
+/**
+ * @addtogroup hooks
+ * @{
+ */
+
+/**
+ * Alter the top of the display for the Views UI.
+ *
+ * This hook can be implemented by themes.
+ *
+ * @param array[] $build
+ *   Render array for the display top.
+ * @param \Drupal\views_ui\ViewUI $view
+ *   The view being edited.
+ * @param string $display_id
+ *   The display ID.
+ *
+ * @todo Until https://www.drupal.org/project/drupal/issues/3087455 is resolved,
+ *   use this hook or hook_views_ui_display_tab_alter() instead of
+ *   hook_form_view_edit_form_alter().
+ *
+ * @see \Drupal\views_ui\ViewUI::renderDisplayTop()
+ */
+function hook_views_ui_display_top_alter(&$build, \Drupal\views_ui\ViewUI $view, $display_id) {
+  $build['custom']['#markup'] = 'This text should always appear';
+}
+
+/**
+ * Alter the renderable array representing the edit page for one display.
+ *
+ * This hook can be implemented by themes.
+ *
+ * @param array[] $build
+ *   Render array for the tab contents.
+ * @param \Drupal\views_ui\ViewUI $view
+ *   The view being edited.
+ * @param string $display_id
+ *   The display ID.
+ *
+ * @todo Until https://www.drupal.org/project/drupal/issues/3087455 is resolved,
+ *   use this hook or hook_views_ui_display_tab_alter() instead of
+ *   hook_form_view_edit_form_alter().
+ *
+ * @see \Drupal\views_ui\ViewEditForm::getDisplayTab()
+ */
+function hook_views_ui_display_tab_alter(&$build, \Drupal\views_ui\ViewUI $view, $display_id) {
+  $build['custom']['#markup'] = 'This text should always appear';
+}
+
+/**
+ * @} End of "addtogroup hooks".
+ */