diff --git a/core/lib/Drupal/Core/Link.php b/core/lib/Drupal/Core/Link.php
index e435d44279..a681bbccf9 100644
--- a/core/lib/Drupal/Core/Link.php
+++ b/core/lib/Drupal/Core/Link.php
@@ -29,6 +29,40 @@ class Link implements RenderableInterface {
    */
   protected $url;
 
+  /**
+   * The link attributes.
+   *
+   * @var array
+   */
+  protected $attributes = [];
+
+  /**
+   * An indicator of whether this is a dialog link.
+   *
+   * @var bool
+   */
+  protected $isDialog = FALSE;
+
+  /**
+   * Returns the link attributes.
+   *
+   * @return array
+   *   The link attributes.
+   */
+  public function getAttributes() {
+    return $this->attributes;
+  }
+
+  /**
+   * Sets the link attributes.
+   *
+   * @param array $attributes
+   *   The link attributes.
+   */
+  public function setAttributes(array $attributes) {
+    $this->attributes = $attributes;
+  }
+
   /**
    * Constructs a new Link object.
    *
@@ -140,11 +174,58 @@ public function toString() {
    * {@inheritdoc}
    */
   public function toRenderable() {
-    return [
+    $renderable = [
       '#type' => 'link',
       '#url' => $this->url,
       '#title' => $this->text,
     ];
+    if ($this->attributes) {
+      $renderable['#attributes'] = $this->attributes;
+    }
+    if ($this->isDialog) {
+      $renderable['#attached'] = [
+        'library' => [
+          'core/drupal.dialog.ajax',
+        ],
+      ];
+    }
+    return $renderable;
+  }
+
+  /**
+   * 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.
+   * @param array $options
+   *   The dialog options.
+   */
+  public function openInDailog($type = 'modal', $renderer = NULL, $options = []) {
+    if (!in_array($type, ['dialog', 'modal'])) {
+      throw new \UnexpectedValueException("The dialog type must be either 'dialog' or 'modal'");
+    }
+    // @todo Do we actually need to check this?
+    $main_content_renders = \Drupal::getContainer()->getParameter('main_content_renderers');
+    $renderer_key = "drupal_$type" . ($renderer ? ".$renderer" : '');
+    if (!isset($main_content_renders[$renderer_key])) {
+      throw new \UnexpectedValueException("The renderer '$renderer_key' is not available.");
+    }
+    if (!isset($this->attributes['class']) || !in_array('use-ajax', $this->attributes['class'])) {
+      $this->attributes['class'][] = 'use-ajax';
+    }
+    $this->attributes['data-dialog-type'] = $type;
+    if ($renderer) {
+      $this->attributes['data-dialog-renderer'] = $renderer;
+    }
+    if ($options) {
+      $this->attributes['data-dialog-options'] = json_encode($options);
+    }
+    $this->isDialog = TRUE;
+
+    return $this;
   }
 
 }
