diff --git a/core/lib/Drupal/Core/Theme/AjaxBasePageNegotiator.php b/core/lib/Drupal/Core/Theme/AjaxBasePageNegotiator.php
index 261cedf..1cd883b 100644
--- a/core/lib/Drupal/Core/Theme/AjaxBasePageNegotiator.php
+++ b/core/lib/Drupal/Core/Theme/AjaxBasePageNegotiator.php
@@ -21,8 +21,6 @@
  * be displayed in either theme, but the Ajax response to the Field module's
  * "Add another item" button should be rendered using the same theme as the rest
  * of the page.
- *
- * Therefore specify '_theme: ajax_base_page' as part of the router options.
  */
 class AjaxBasePageNegotiator implements ThemeNegotiatorInterface {
 
@@ -67,17 +65,15 @@ public function __construct(CsrfTokenGenerator $token_generator, ConfigFactoryIn
    * {@inheritdoc}
    */
   public function applies(RouteMatchInterface $route_match) {
-    // Check whether the route was configured to use the base page theme.
-    return ($route = $route_match->getRouteObject())
-      && $route->hasOption('_theme')
-      && $route->getOption('_theme') == 'ajax_base_page';
+    $ajax_page_state = $this->requestStack->getCurrentRequest()->request->get('ajax_page_state');
+    return !empty($ajax_page_state['theme']) && isset($ajax_page_state['theme_token']);
   }
 
   /**
    * {@inheritdoc}
    */
   public function determineActiveTheme(RouteMatchInterface $route_match) {
-    if (($ajax_page_state = $this->requestStack->getCurrentRequest()->request->get('ajax_page_state'))  && !empty($ajax_page_state['theme']) && !empty($ajax_page_state['theme_token'])) {
+    if ($ajax_page_state = $this->requestStack->getCurrentRequest()->request->get('ajax_page_state')) {
       $theme = $ajax_page_state['theme'];
       $token = $ajax_page_state['theme_token'];
 
diff --git a/core/modules/contextual/contextual.routing.yml b/core/modules/contextual/contextual.routing.yml
index b44d619..8ab2f28 100644
--- a/core/modules/contextual/contextual.routing.yml
+++ b/core/modules/contextual/contextual.routing.yml
@@ -2,7 +2,5 @@ contextual.render:
   path: '/contextual/render'
   defaults:
     _controller: '\Drupal\contextual\ContextualController::render'
-  options:
-    _theme: ajax_base_page
   requirements:
     _permission: 'access contextual links'
diff --git a/core/modules/editor/editor.routing.yml b/core/modules/editor/editor.routing.yml
index cacdfc7..ad40c2f 100644
--- a/core/modules/editor/editor.routing.yml
+++ b/core/modules/editor/editor.routing.yml
@@ -2,8 +2,6 @@ editor.filter_xss:
   path: '/editor/filter_xss/{filter_format}'
   defaults:
     _controller: '\Drupal\editor\EditorController::filterXss'
-  options:
-    _theme: ajax_base_page
   requirements:
     _entity_access: 'filter_format.use'
 
@@ -12,7 +10,6 @@ editor.field_untransformed_text:
   defaults:
     _controller: '\Drupal\editor\EditorController::getUntransformedText'
   options:
-    _theme: ajax_base_page
     parameters:
       entity:
         type: entity:{entity_type}
@@ -25,8 +22,6 @@ editor.image_dialog:
   defaults:
     _form: '\Drupal\editor\Form\EditorImageDialog'
     _title: 'Upload image'
-  options:
-    _theme: ajax_base_page
   requirements:
     _entity_access: 'filter_format.use'
 
@@ -35,7 +30,5 @@ editor.link_dialog:
   defaults:
     _form: '\Drupal\editor\Form\EditorLinkDialog'
     _title: 'Add link'
-  options:
-    _theme: ajax_base_page
   requirements:
     _entity_access: 'filter_format.use'
diff --git a/core/modules/quickedit/quickedit.routing.yml b/core/modules/quickedit/quickedit.routing.yml
index 489d855..af22056 100644
--- a/core/modules/quickedit/quickedit.routing.yml
+++ b/core/modules/quickedit/quickedit.routing.yml
@@ -2,8 +2,6 @@ quickedit.metadata:
   path: '/quickedit/metadata'
   defaults:
     _controller: '\Drupal\quickedit\QuickEditController::metadata'
-  options:
-    _theme: ajax_base_page
   requirements:
     _permission: 'access in-place editing'
 
@@ -19,7 +17,6 @@ quickedit.field_form:
   defaults:
     _controller: '\Drupal\quickedit\QuickEditController::fieldForm'
   options:
-    _theme: ajax_base_page
     parameters:
       entity:
         type: entity:{entity_type}
diff --git a/core/modules/system/tests/modules/ajax_test/ajax_test.routing.yml b/core/modules/system/tests/modules/ajax_test/ajax_test.routing.yml
index d22be40..e8d06c0 100644
--- a/core/modules/system/tests/modules/ajax_test/ajax_test.routing.yml
+++ b/core/modules/system/tests/modules/ajax_test/ajax_test.routing.yml
@@ -35,12 +35,17 @@ ajax_test.render:
   requirements:
     _access: 'TRUE'
 
+ajax_test.admin.theme:
+  path: '/admin/ajax-test/theme'
+  defaults:
+    _controller: '\Drupal\ajax_test\Controller\AjaxTestController::theme'
+  requirements:
+    _access: 'TRUE'
+
 ajax_test.order:
   path: '/ajax-test/order'
   defaults:
     _controller: '\Drupal\ajax_test\Controller\AjaxTestController::order'
-  options:
-    _theme: ajax_base_page
   requirements:
     _access: 'TRUE'
 
diff --git a/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php b/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php
index fbbb2b5..b815be3 100644
--- a/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php
+++ b/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php
@@ -62,6 +62,15 @@ public function render() {
   }
 
   /**
+   * Returns the used theme.
+   */
+  public function theme() {
+    return [
+      '#markup' => 'Current theme: ' . \Drupal::theme()->getActiveTheme()->getName(),
+    ];
+  }
+
+  /**
    * Returns an AjaxResponse; settings command set last.
    *
    * Helps verifying AjaxResponse reorders commands to ensure correct execution.
@@ -184,6 +193,17 @@ public function dialog() {
             ))
           ),
         ),
+        'link8' => [
+          'title' => 'Link 8 (ajax)',
+          'url' => Url::fromRoute('ajax_test.admin.theme'),
+          'attributes' => [
+            'class' => ['use-ajax'],
+            'data-dialog-type' => 'modal',
+            'data-dialog-options' => json_encode([
+              'width' => 400,
+            ])
+          ],
+        ]
       ),
     );
 
diff --git a/core/modules/toolbar/toolbar.routing.yml b/core/modules/toolbar/toolbar.routing.yml
index 0f9dcf8..10e6cc3 100644
--- a/core/modules/toolbar/toolbar.routing.yml
+++ b/core/modules/toolbar/toolbar.routing.yml
@@ -2,7 +2,5 @@ toolbar.subtrees:
   path: '/toolbar/subtrees/{hash}'
   defaults:
     _controller: '\Drupal\toolbar\Controller\ToolbarController::subtreesAjax'
-  options:
-    _theme: ajax_base_page
   requirements:
     _custom_access: '\Drupal\toolbar\Controller\ToolbarController::checkSubTreeAccess'
diff --git a/core/modules/views/views.routing.yml b/core/modules/views/views.routing.yml
index 3d84341..e720603 100644
--- a/core/modules/views/views.routing.yml
+++ b/core/modules/views/views.routing.yml
@@ -2,8 +2,6 @@ views.ajax:
   path: '/views/ajax'
   defaults:
     _controller: '\Drupal\views\Controller\ViewAjaxController::ajaxView'
-  options:
-    _theme: ajax_base_page
   requirements:
     _access: 'TRUE'
 
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxThemeTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxThemeTest.php
new file mode 100644
index 0000000..c536fe8
--- /dev/null
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxThemeTest.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Drupal\FunctionalJavascriptTests\Ajax;
+
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+
+/**
+ * Tests that AJAX responses use the current theme.
+ *
+ * @group Ajax
+ */
+class AjaxThemeTest extends JavascriptTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['ajax_test'];
+
+  public function testAjaxWithAdminRoute() {
+    \Drupal::service('theme_installer')->install(['stable', 'seven']);
+    $theme_config = \Drupal::configFactory()->getEditable('system.theme');
+    $theme_config->set('admin', 'seven');
+    $theme_config->set('default', 'stable');
+    $theme_config->save();
+
+    $account = $this->drupalCreateUser(['view the administration theme']);
+    $this->drupalLogin($account);
+
+    // First visit the site directly via the URL. This should render it in the
+    // admin theme.
+    $this->drupalGet('admin/ajax-test/theme');
+    $assert = $this->assertSession();
+    $assert->pageTextContains('Current theme: seven');
+
+    // Now
+    $this->drupalGet('ajax-test/dialog');
+    $assert->pageTextNotContains('Current theme: stable');
+    $this->clickLink('Link 8 (ajax)');
+    $this->waitForAjaxToFinish();
+
+    $assert->pageTextContains('Current theme: stable');
+    $assert->pageTextNotContains('Current theme: seven');
+  }
+
+  /**
+   * Waits for jQuery to become active and animations to complete.
+   */
+  protected function waitForAjaxToFinish() {
+    $condition = "(0 === jQuery.active && 0 === jQuery(':animated').length)";
+    $this->assertJsCondition($condition, 10000);
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Theme/AjaxBasePageNegotiatorTest.php b/core/tests/Drupal/Tests/Core/Theme/AjaxBasePageNegotiatorTest.php
new file mode 100644
index 0000000..24746c3
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Theme/AjaxBasePageNegotiatorTest.php
@@ -0,0 +1,127 @@
+<?php
+
+namespace Drupal\Tests\Core\Theme;
+
+use Drupal\Core\Access\CsrfTokenGenerator;
+use Drupal\Core\Routing\RouteMatch;
+use Drupal\Core\Theme\AjaxBasePageNegotiator;
+use Drupal\Tests\UnitTestCase;
+use Prophecy\Argument;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Theme\AjaxBasePageNegotiator
+ * @group Theme
+ */
+class AjaxBasePageNegotiatorTest extends UnitTestCase {
+
+  /**
+   * @var \Drupal\Core\Theme\AjaxBasePageNegotiator
+   *
+   * The AJAX base page negotiator.
+   */
+  protected $negotiator;
+
+  /**
+   * @var \Drupal\Core\Access\CsrfTokenGenerator|\Prophecy\Prophecy\ProphecyInterface
+   *
+   * The CSRF token generator.
+   */
+  protected $tokenGenerator;
+
+  /**
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   *
+   * The request stack.
+   */
+  protected $requestStack;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->tokenGenerator = $this->prophesize(CsrfTokenGenerator::class);
+    $config_factory = $this->getConfigFactoryStub(['system.theme' => ['default' => 'bartik']]);
+    $this->requestStack = new RequestStack();
+    $this->negotiator = new AjaxBasePageNegotiator($this->tokenGenerator->reveal(), $config_factory, $this->requestStack);
+  }
+
+  /**
+   * @covers ::applies
+   * @dataProvider providerTestApplies
+   */
+  public function testApplies($request_data, $expected) {
+    $request = new Request([], $request_data);
+    $route_match = RouteMatch::createFromRequest($request);
+    $this->requestStack->push($request);
+
+    $result = $this->negotiator->applies($route_match);
+    $this->assertSame($expected, $result);
+  }
+
+  public function providerTestApplies() {
+    $data = [];
+    $data['empty'] = [[], FALSE];
+    $data['no_theme'] = [['ajax_page_state' => ['theme' => '', 'theme_token' => '']], FALSE];
+    $data['valid_theme_empty_theme_token'] = [['ajax_page_state' => ['theme' => 'seven', 'theme_token' => '']], TRUE];
+    $data['valid_theme_valid_theme_token'] = [['ajax_page_state' => ['theme' => 'seven', 'theme_token' => 'valid_theme_token']], TRUE];
+    return $data;
+  }
+
+  /**
+   * @covers ::determineActiveTheme
+   */
+  public function testDetermineActiveThemeValidToken() {
+    $theme = 'seven';
+    $theme_token = 'valid_theme_token';
+
+    $request = new Request([], ['ajax_page_state' => ['theme' => $theme, 'theme_token' => $theme_token]]);
+    $this->requestStack->push($request);
+    $route_match = RouteMatch::createFromRequest($request);
+
+    $this->tokenGenerator->validate($theme_token, $theme)->willReturn(TRUE);
+
+    $result = $this->negotiator->determineActiveTheme($route_match);
+    $this->assertSame($theme, $result);
+  }
+
+  /**
+   * @covers ::determineActiveTheme
+   */
+  public function testDetermineActiveThemeInvalidToken() {
+    $theme = 'seven';
+    $theme_token = 'invalid_theme_token';
+
+    $request = new Request([], ['ajax_page_state' => ['theme' => $theme, 'theme_token' => $theme_token]]);
+    $this->requestStack->push($request);
+    $route_match = RouteMatch::createFromRequest($request);
+
+    $this->tokenGenerator->validate($theme_token, $theme)->willReturn(FALSE);
+
+    $result = $this->negotiator->determineActiveTheme($route_match);
+    $this->assertSame(NULL, $result);
+  }
+
+  /**
+   * @covers ::determineActiveTheme
+   */
+  public function testDetermineActiveThemeDefaultTheme() {
+    $theme = 'bartik';
+    // When the theme is the system default, an empty string is provided as the
+    // theme token. See system_js_settings_alter().
+    $theme_token = '';
+
+    $request = new Request([], ['ajax_page_state' => ['theme' => $theme, 'theme_token' => $theme_token]]);
+    $this->requestStack->push($request);
+    $route_match = RouteMatch::createFromRequest($request);
+
+    $this->tokenGenerator->validate(Argument::cetera())->shouldNotBeCalled();
+
+    $result = $this->negotiator->determineActiveTheme($route_match);
+    $this->assertSame($theme, $result);
+  }
+
+}
