diff --git a/core/lib/Drupal/Core/Block/BlockBase.php b/core/lib/Drupal/Core/Block/BlockBase.php
index ee9078335b..745ec498bb 100644
--- a/core/lib/Drupal/Core/Block/BlockBase.php
+++ b/core/lib/Drupal/Core/Block/BlockBase.php
@@ -169,10 +169,11 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
       '#required' => TRUE,
     ];
     $form['label_display'] = [
-      '#type' => 'checkbox',
+      '#type' => 'select',
       '#title' => $this->t('Display title'),
-      '#default_value' => ($this->configuration['label_display'] === static::BLOCK_LABEL_VISIBLE),
-      '#return_value' => static::BLOCK_LABEL_VISIBLE,
+      '#description' => $this->t('How to display the block title.'),
+      '#options' => $this->getLabelDisplayOptions(),
+      '#default_value' => $this->configuration['label_display'],
     ];
 
     // Add context mapping UI form elements.
@@ -184,6 +185,20 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
   }
 
   /**
+   * Returns an array of visibility options for the block label (title).
+   *
+   * @return array
+   *   An array of visibility options.
+   */
+  protected function getLabelDisplayOptions() {
+    return [
+      static::BLOCK_LABEL_VISIBLE => $this->t('Visible'),
+      static::BLOCK_LABEL_HIDDEN => '- ' . $this->t('Hidden') . ' -',
+      static::BLOCK_LABEL_VISUALLY_HIDDEN => '- ' . $this->t('Visually Hidden') . ' -',
+    ];
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function blockForm($form, FormStateInterface $form_state) {
diff --git a/core/lib/Drupal/Core/Block/BlockPluginInterface.php b/core/lib/Drupal/Core/Block/BlockPluginInterface.php
index b3cf88e0b8..26624faf1f 100644
--- a/core/lib/Drupal/Core/Block/BlockPluginInterface.php
+++ b/core/lib/Drupal/Core/Block/BlockPluginInterface.php
@@ -28,6 +28,19 @@
   const BLOCK_LABEL_VISIBLE = 'visible';
 
   /**
+   * Indicates the block label (title) should hidden from end users.
+   *
+   * @todo This value should be changed to "hidden" in Drupal 9.0.0 for
+   *   consistency. See https://www.drupal.org/node/2863313.
+   */
+  const BLOCK_LABEL_HIDDEN = '0';
+
+  /**
+   * Indicates the block label (title) should be visually-hidden from end users.
+   */
+  const BLOCK_LABEL_VISUALLY_HIDDEN = 'visually_hidden';
+
+  /**
    * Returns the user-facing block label.
    *
    * @todo Provide other specific label-related methods in
diff --git a/core/modules/block/block.module b/core/modules/block/block.module
index ee6a4a11ea..430d33ae6c 100644
--- a/core/modules/block/block.module
+++ b/core/modules/block/block.module
@@ -6,6 +6,7 @@
  */
 
 use Drupal\Component\Utility\Html;
+use Drupal\Core\Block\BlockPluginInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Url;
 use Drupal\language\ConfigurableLanguageInterface;
@@ -212,14 +213,19 @@ function template_preprocess_block(&$variables) {
   $variables['plugin_id'] = $variables['elements']['#plugin_id'];
   $variables['base_plugin_id'] = $variables['elements']['#base_plugin_id'];
   $variables['derivative_plugin_id'] = $variables['elements']['#derivative_plugin_id'];
-  $variables['label'] = !empty($variables['configuration']['label_display']) ? $variables['configuration']['label'] : '';
+  $variables['label'] = $variables['configuration']['label_display'] != BlockPluginInterface::BLOCK_LABEL_HIDDEN ? $variables['configuration']['label'] : '';
   $variables['content'] = $variables['elements']['content'];
   // A block's label is configuration: it is static. Allow dynamic labels to be
   // set in the render array.
-  if (isset($variables['elements']['content']['#title']) && !empty($variables['configuration']['label_display'])) {
+  if (isset($variables['elements']['content']['#title']) && $variables['configuration']['label_display'] != BlockPluginInterface::BLOCK_LABEL_HIDDEN) {
     $variables['label'] = $variables['elements']['content']['#title'];
   }
 
+  // Add class visually-hidden class (if needed) to improve accessibility.
+  if (isset($variables['label']) && $variables['configuration']['label_display'] == BlockPluginInterface::BLOCK_LABEL_VISUALLY_HIDDEN) {
+    $variables['title_attributes']['class'] = 'visually-hidden';
+  }
+
   // Create a valid HTML ID and make sure it is unique.
   if (!empty($variables['elements']['#id'])) {
     $variables['attributes']['id'] = Html::getUniqueId('block-' . $variables['elements']['#id']);
diff --git a/core/modules/block/tests/modules/block_test/config/install/block.block.test_block.yml b/core/modules/block/tests/modules/block_test/config/install/block.block.test_block.yml
index be7e06ce0e..f3f149fbd1 100644
--- a/core/modules/block/tests/modules/block_test/config/install/block.block.test_block.yml
+++ b/core/modules/block/tests/modules/block_test/config/install/block.block.test_block.yml
@@ -8,7 +8,7 @@ plugin: test_html
 settings:
   label: 'Test HTML block'
   provider: block_test
-  label_display: 'hidden'
+  label_display: '0'
 dependencies:
   module:
     - block_test
diff --git a/core/modules/block/tests/src/Functional/BlockTest.php b/core/modules/block/tests/src/Functional/BlockTest.php
index 300205247c..355221f7c2 100644
--- a/core/modules/block/tests/src/Functional/BlockTest.php
+++ b/core/modules/block/tests/src/Functional/BlockTest.php
@@ -4,6 +4,8 @@
 
 use Drupal\Component\Utility\Html;
 use Drupal\block\Entity\Block;
+use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Core\Block\BlockPluginInterface;
 use Drupal\Core\Url;
 use Drupal\user\Entity\Role;
 use Drupal\user\RoleInterface;
@@ -28,7 +30,7 @@ public function testBlockVisibility() {
       'id' => strtolower($this->randomMachineName(8)),
       'region' => 'sidebar_first',
       'settings[label]' => $title,
-      'settings[label_display]' => TRUE,
+      'settings[label_display]' => BlockPluginInterface::BLOCK_LABEL_VISIBLE,
     ];
     // Set the block to be hidden on any user path, and to be shown only to
     // authenticated users.
@@ -188,7 +190,7 @@ public function testBlock() {
     $block = [];
     $block['id'] = 'system_powered_by_block';
     $block['settings[label]'] = $this->randomMachineName(8);
-    $block['settings[label_display]'] = TRUE;
+    $block['settings[label_display]'] = BlockPluginInterface::BLOCK_LABEL_VISIBLE;
     $block['theme'] = $this->config('system.theme')->get('default');
     $block['region'] = 'header';
 
@@ -296,24 +298,46 @@ public function testHideBlockTitle() {
       'id' => $id,
       'region' => 'sidebar_first',
       'settings[label]' => $title,
+      'settings[label_display]' => BlockPluginInterface::BLOCK_LABEL_VISIBLE,
     ];
     $this->drupalPostForm('admin/structure/block/add/' . $block_name . '/' . $default_theme, $edit, t('Save block'));
     $this->assertText('The block configuration has been saved.', 'Block was saved');
 
     $this->drupalGet('user');
-    $this->assertNoText($title, 'Block title was not displayed by default.');
+    $this->assertText($title, 'Block title was displayed by default.');
 
     $edit = [
-      'settings[label_display]' => TRUE,
+      'settings[label_display]' => BlockPluginInterface::BLOCK_LABEL_HIDDEN,
     ];
     $this->drupalPostForm('admin/structure/block/manage/' . $id, $edit, t('Save block'));
     $this->assertText('The block configuration has been saved.', 'Block was saved');
 
     $this->drupalGet('admin/structure/block/manage/' . $id);
-    $this->assertFieldChecked('edit-settings-label-display', 'The display_block option has the correct default value on the configuration form.');
+    // Assert option selected with Drupal Selector.
+    $message = 'The label_display option has the correct default value on the configuration form.';
+    $elements = $this->xpath('//select[@data-drupal-selector=:data_drupal_selector]//option[@value=:option]', [':data_drupal_selector' => 'edit-settings-label-display', ':option' => BlockPluginInterface::BLOCK_LABEL_HIDDEN]);
+    $element_value = $elements[0]->getText();
+    $this->assertTrue(isset($element_value) && !empty($element_value), $message ? $message : SafeMarkup::format('Option @option for field @data_drupal_selector is selected.', ['@option' => BlockPluginInterface::BLOCK_LABEL_HIDDEN, '@data_drupal_selector' => 'edit-settings-label-display']), 'Browser');
 
     $this->drupalGet('user');
-    $this->assertText($title, 'Block title was displayed when enabled.');
+    $this->assertNoText($title, 'Block title was not displayed anymore when disabled.');
+
+    // Visually hide the title of the block.
+    $edit = [
+      'settings[label_display]' => BlockPluginInterface::BLOCK_LABEL_VISUALLY_HIDDEN,
+    ];
+    $this->drupalPostForm('admin/structure/block/manage/' . $id, $edit, t('Save block'));
+    $this->assertText('The block configuration has been saved.', 'Block was saved');
+
+    $this->drupalGet('admin/structure/block/manage/' . $id);
+    // Assert option selected with Drupal Selector.
+    $message = 'The label_display option has the correct default value on the configuration form.';
+    $elements = $this->xpath('//select[@data-drupal-selector=:data_drupal_selector]//option[@value=:option]', [':data_drupal_selector' => 'edit-settings-label-display', ':option' => BlockPluginInterface::BLOCK_LABEL_VISUALLY_HIDDEN]);
+    $element_value = $elements[0]->getText();
+    $this->assertTrue(isset($element_value) && !empty($element_value), $message ? $message : SafeMarkup::format('Option @option for field @data_drupal_selector is selected.', ['@option' => BlockPluginInterface::BLOCK_LABEL_VISUALLY_HIDDEN, '@data_drupal_selector' => 'edit-settings-label-display']), 'Browser');
+
+    $this->drupalGet('user');
+    $this->assertFieldByXPath('//*[@id="block-' . $id . '"]/h2[contains(@class, "visually-hidden")]', $title, 'Block title was displayed in the source code but with the visually-hidden CSS class when configured be visually hidden.');
   }
 
   /**
diff --git a/core/modules/block/tests/src/Functional/Views/DisplayBlockTest.php b/core/modules/block/tests/src/Functional/Views/DisplayBlockTest.php
index d12209944a..4e433866ad 100644
--- a/core/modules/block/tests/src/Functional/Views/DisplayBlockTest.php
+++ b/core/modules/block/tests/src/Functional/Views/DisplayBlockTest.php
@@ -3,6 +3,7 @@
 namespace Drupal\Tests\block\Functional\Views;
 
 use Drupal\Component\Serialization\Json;
+use Drupal\Core\Block\BlockPluginInterface;
 use Drupal\Core\Url;
 use Drupal\Tests\block\Functional\AssertBlockAppearsTrait;
 use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
@@ -260,7 +261,7 @@ public function testBlockRendering() {
     $this->assertEqual($result[0]->getText(), 'test_view_block');
 
     // Hide the title.
-    $block->getPlugin()->setConfigurationValue('label_display', FALSE);
+    $block->getPlugin()->setConfigurationValue('label_display', BlockPluginInterface::BLOCK_LABEL_HIDDEN);
     $block->save();
 
     $this->drupalGet('');
diff --git a/core/modules/block/tests/src/Kernel/BlockInterfaceTest.php b/core/modules/block/tests/src/Kernel/BlockInterfaceTest.php
index 48d63a302d..8cae75d23b 100644
--- a/core/modules/block/tests/src/Kernel/BlockInterfaceTest.php
+++ b/core/modules/block/tests/src/Kernel/BlockInterfaceTest.php
@@ -70,10 +70,15 @@ public function testBlockInterface() {
         '#required' => TRUE,
       ],
       'label_display' => [
-        '#type' => 'checkbox',
+        '#type' => 'select',
         '#title' => 'Display title',
-        '#default_value' => TRUE,
-        '#return_value' => 'visible',
+        '#description' => 'How to display the block title.',
+        '#options' => [
+          BlockPluginInterface::BLOCK_LABEL_VISIBLE => 'Visible',
+          BlockPluginInterface::BLOCK_LABEL_HIDDEN => '- Hidden -',
+          BlockPluginInterface::BLOCK_LABEL_VISUALLY_HIDDEN => '- Visually Hidden -',
+        ],
+        '#default_value' => BlockPluginInterface::BLOCK_LABEL_VISIBLE,
       ],
       'context_mapping' => [],
       'display_message' => [
diff --git a/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php b/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php
index d2881eb599..854d9b2a6f 100644
--- a/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php
+++ b/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php
@@ -7,6 +7,7 @@
 use Drupal\block_content\Entity\BlockContentType;
 use Drupal\settings_tray_test\Plugin\Block\SettingsTrayFormAnnotationIsClassBlock;
 use Drupal\settings_tray_test\Plugin\Block\SettingsTrayFormAnnotationNoneBlock;
+use Drupal\Core\Block\BlockPluginInterface;
 use Drupal\user\Entity\Role;
 
 /**
@@ -98,11 +99,11 @@ public function testBlocks($block_plugin, $new_page_text, $element_selector, $la
       $this->openBlockForm($block_selector);
       switch ($block_plugin) {
         case 'system_powered_by_block':
-          // Confirm "Display Title" is not checked.
-          $web_assert->checkboxNotChecked('settings[label_display]');
+          // Confirm "Display Title" is not set to 'visible'.
+          $web_assert->fieldValueNotEquals('settings[label_display]', BlockPluginInterface::BLOCK_LABEL_VISIBLE);
           // Confirm Title is not visible.
           $this->assertEquals($this->isLabelInputVisible(), FALSE, 'Label is not visible');
-          $page->checkField('settings[label_display]');
+          $page->selectFieldOption('settings[label_display]', BlockPluginInterface::BLOCK_LABEL_VISIBLE);
           $this->assertEquals($this->isLabelInputVisible(), TRUE, 'Label is visible');
           // Fill out form, save the form.
           $page->fillField('settings[label]', $new_page_text);
@@ -111,7 +112,7 @@ public function testBlocks($block_plugin, $new_page_text, $element_selector, $la
 
         case 'system_branding_block':
           // Fill out form, save the form.
-          $page->fillField('settings[site_information][site_name]', $new_page_text);
+          $page->selectFieldOption('settings[label_display]', BlockPluginInterface::BLOCK_LABEL_HIDDEN);
           break;
 
         case 'settings_tray_test_class':
@@ -231,8 +232,9 @@ protected function assertOffCanvasBlockFormIsValid() {
     $web_assert = $this->assertSession();
     // Confirm that Block title display label has been changed.
     $web_assert->elementTextContains('css', '.form-item-settings-label-display label', 'Display block title');
-    // Confirm Block title label is shown if checkbox is checked.
-    if ($this->getSession()->getPage()->find('css', 'input[name="settings[label_display]"]')->isChecked()) {
+    // Confirm Block title label is shown if the label_display setting is set to
+    // 'visible'.
+    if ($this->getSession()->getPage()->find('css', 'select[name="settings[label_display]"]')->getValue() == BlockPluginInterface::BLOCK_LABEL_VISIBLE) {
       $this->assertEquals($this->isLabelInputVisible(), TRUE, 'Label is visible');
       $web_assert->elementTextContains('css', '.form-item-settings-label label', 'Block title');
     }
@@ -242,7 +244,7 @@ protected function assertOffCanvasBlockFormIsValid() {
 
     // Check that common block form elements exist.
     $web_assert->elementExists('css', static::LABEL_INPUT_SELECTOR);
-    $web_assert->elementExists('css', 'input[data-drupal-selector="edit-settings-label-display"]');
+    $web_assert->elementExists('css', 'select[data-drupal-selector="edit-settings-label-display"]');
     // Check that advanced block form elements do not exist.
     $web_assert->elementNotExists('css', 'input[data-drupal-selector="edit-visibility-request-path-pages"]');
     $web_assert->elementNotExists('css', 'select[data-drupal-selector="edit-region"]');
