diff --git a/core/lib/Drupal/Core/Link.php b/core/lib/Drupal/Core/Link.php
index e435d44279..e84df99595 100644
--- a/core/lib/Drupal/Core/Link.php
+++ b/core/lib/Drupal/Core/Link.php
@@ -147,4 +147,16 @@ public function toRenderable() {
     ];
   }
 
+  /**
+   * Helper function to access \Drupal\Core\Url::openInDialog().
+   *
+   * @see \Drupal\Core\Url::openInDialog()
+   *
+   * @return $this
+   */
+  public function openInDialog($type = 'modal', $renderer = NULL, array $options = []) {
+    $this->getUrl()->openInDialog($type, $renderer, $options);
+    return $this;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Render/Element/Link.php b/core/lib/Drupal/Core/Render/Element/Link.php
index 216f068a49..8ab992781e 100644
--- a/core/lib/Drupal/Core/Render/Element/Link.php
+++ b/core/lib/Drupal/Core/Render/Element/Link.php
@@ -89,6 +89,11 @@ public static function preRenderLink($element) {
 
     if (!empty($element['#url']) && $element['#url'] instanceof CoreUrl) {
       $options = NestedArray::mergeDeep($element['#url']->getOptions(), $element['#options']);
+      if (!empty($options['attributes']['data-dialog-type'])) {
+        if (empty($element['#attached']['library']) || !in_array('core/drupal.dialog.ajax', $element['#attached']['library'])) {
+          $element['#attached']['library'][] = 'core/drupal.dialog.ajax';
+        }
+      }
       /** @var \Drupal\Core\Utility\LinkGenerator $link_generator */
       $link_generator = \Drupal::service('link_generator');
       $generated_link = $link_generator->generate($element['#title'], $element['#url']->setOptions($options));
diff --git a/core/lib/Drupal/Core/Url.php b/core/lib/Drupal/Core/Url.php
index 76dc46c77b..a0e7810d11 100644
--- a/core/lib/Drupal/Core/Url.php
+++ b/core/lib/Drupal/Core/Url.php
@@ -92,6 +92,13 @@ class Url {
    */
   protected $internalPath;
 
+  /**
+   * The link attributes that are needed for dialog use.
+   *
+   * @var array
+   */
+  protected $dialogAttributes;
+
   /**
    * Constructs a new Url object.
    *
@@ -655,6 +662,7 @@ public function getOption($name) {
    */
   public function setOptions($options) {
     $this->options = $options;
+    $this->setDialogAttributes();
     return $this;
   }
 
@@ -672,6 +680,9 @@ public function setOptions($options) {
    */
   public function setOption($name, $value) {
     $this->options[$name] = $value;
+    if ($name == 'attributes') {
+      $this->setDialogAttributes();
+    }
     return $this;
   }
 
@@ -887,4 +898,49 @@ public function setUnroutedUrlAssembler(UnroutedUrlAssemblerInterface $url_assem
     return $this;
   }
 
+  /**
+   * Changes the link to open in a dialog.
+   *
+   * @param string $type
+   *   The dialog type 'modal' or 'dialog', defaults to 'modal'.
+   * @param string $renderer
+   *   The dialog renderer. Core provides the 'off_canvas' renderer which uses
+   *   the off-canvas dialog. Other modules can provide dialog renderers by
+   *   defining a service that is tagged with the name
+   *   'render.main_content_renderer' and tagged with a format in the pattern of
+   *   'drupal_[dialogType].[dialogRenderer]'.
+   * @param array $options
+   *   The dialog options.
+   *
+   * @return $this
+   */
+  public function openInDialog($type = 'modal', $renderer = NULL, array $options = []) {
+    assert(in_array($type, ['dialog', 'modal']), "Invalid dialog type: '$type'.  The dialog type must be either 'dialog' or 'modal'");
+    $main_content_renders = \Drupal::getContainer()->getParameter('main_content_renderers');
+    $renderer_key = "drupal_$type" . ($renderer ? ".$renderer" : '');
+    assert(isset($main_content_renders[$renderer_key]), "The renderer '$renderer_key' is not available.");
+    $this->dialogAttributes['data-dialog-type'] = $type;
+    if ($renderer) {
+      $this->dialogAttributes['data-dialog-renderer'] = $renderer;
+    }
+    if ($options) {
+      $this->dialogAttributes['data-dialog-options'] = json_encode($options);
+    }
+    $this->setDialogAttributes();
+    return $this;
+  }
+
+  /**
+   * Sets the URL attributes to use a dialog if needed.
+   */
+  protected function setDialogAttributes() {
+    if ($this->dialogAttributes) {
+      $attributes = $this->getOption('attributes');
+      if (!$attributes || !isset($attributes['class']) || !in_array('use-ajax', $attributes['class'])) {
+        $attributes['class'][] = 'use-ajax';
+      }
+      $this->options['attributes'] = array_merge($attributes, $this->dialogAttributes);
+    }
+  }
+
 }
