diff --git a/core/core.services.yml b/core/core.services.yml
index d72d30c..fb656bf 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -1010,6 +1010,7 @@ services:
       - { name: twig.extension, priority: 100 }
     calls:
       - [setGenerators, ['@url_generator']]
+      - [setLinkGenerator, ['@link_generator']]
   # @todo Figure out what to do about debugging functions.
   # @see http://drupal.org/node/1804998
   twig.extension.debug:
diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index 51dcd9a..0780a1d 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -308,64 +308,6 @@
  */
 
 /**
- * Implements template_preprocess_HOOK() for theme_menu_tree().
- */
-function template_preprocess_menu_tree(&$variables) {
-  if (isset($variables['tree']['#heading'])) {
-    $variables['heading'] = $variables['tree']['#heading'];
-    $heading = &$variables['heading'];
-    // Convert a string heading into an array, using a H2 tag by default.
-    if (is_string($heading)) {
-      $heading = array('text' => $heading);
-    }
-    // Merge in default array properties into $heading.
-    $heading += array(
-      'level' => 'h2',
-      'attributes' => array(),
-    );
-    // Convert the attributes array into an Attribute object.
-    $heading['attributes'] = new Attribute($heading['attributes']);
-    $heading['text'] = String::checkPlain($heading['text']);
-  }
-
-  if (isset($variables['tree']['#attributes'])) {
-    $variables['attributes'] = new Attribute($variables['tree']['#attributes']);
-  }
-  else {
-    $variables['attributes'] = new Attribute();
-  }
-  if (!isset($variables['attributes']['class'])) {
-    $variables['attributes']['class'] = array();
-  }
-  $variables['attributes']['class'][] = 'menu';
-
-  $variables['tree'] = $variables['tree']['#children'];
-}
-
-/**
- * Returns HTML for a menu link and submenu.
- *
- * @param $variables
- *   An associative array containing:
- *   - element: Structured array data for a menu link.
- *
- * @ingroup themeable
- */
-function theme_menu_link(array $variables) {
-  $element = $variables['element'];
-  $sub_menu = '';
-
-  if ($element['#below']) {
-    $sub_menu = drupal_render($element['#below']);
-  }
-  /** @var \Drupal\Core\Url $url */
-  $url = $element['#url'];
-  $url->setOption('set_active_class', TRUE);
-  $output = \Drupal::linkGenerator()->generateFromUrl($element['#title'], $url);
-  return '<li' . new Attribute($element['#attributes']) . '>' . $output . $sub_menu . "</li>\n";
-}
-
-/**
  * Prepares variables for single local task link templates.
  *
  * Default template: menu-local-task.html.twig.
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 1268e12..0916981 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -2361,12 +2361,9 @@ function drupal_common_theme() {
       'template' => 'pager',
     ),
     // From menu.inc.
-    'menu_link' => array(
-      'render element' => 'element',
-    ),
-    'menu_tree' => array(
-      'render element' => 'tree',
-      'template' => 'menu-tree',
+    'menu' => array(
+      'variables' => array('items' => array(), 'attributes' => []),
+      'template' => 'menu',
     ),
     'menu_local_task' => array(
       'render element' => 'element',
diff --git a/core/lib/Drupal/Core/Menu/MenuLinkTree.php b/core/lib/Drupal/Core/Menu/MenuLinkTree.php
index 25dede7..c015b99 100644
--- a/core/lib/Drupal/Core/Menu/MenuLinkTree.php
+++ b/core/lib/Drupal/Core/Menu/MenuLinkTree.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Controller\ControllerResolverInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Routing\RouteProviderInterface;
+use Drupal\Core\Template\Attribute;
 
 /**
  * Implements the loading, transforming and rendering of menu link trees.
@@ -197,8 +198,8 @@ public function transform(array $tree, array $manipulators) {
   /**
    * {@inheritdoc}
    */
-  public function build(array $tree) {
-    $build = array();
+  public function build(array $tree, $level = 0) {
+    $items = array();
 
     foreach ($tree as $data) {
       $class = array();
@@ -225,31 +226,41 @@ public function build(array $tree) {
       }
 
       // Allow menu-specific theme overrides.
-      $element['#theme'] = 'menu_link__' . strtr($link->getMenuName(), '-', '_');
-      $element['#attributes']['class'] = $class;
-      $element['#title'] = $link->getTitle();
-      $element['#url'] = $link->getUrlObject();
-      $element['#below'] = $data->subtree ? $this->build($data->subtree) : array();
+      $element = array();
+      $element['attributes'] = new Attribute();
+      $element['attributes']['class'] = $class;
+      $element['title'] = $link->getTitle();
+      $element['url'] = $link->getUrlObject();
+      $element['url']->setOption('set_active_class', TRUE);
+      $element['below'] = $data->subtree ? $this->build($data->subtree, $level + 1) : array();
       if (isset($data->options)) {
-        $element['#url']->setOptions(NestedArray::mergeDeep($element['#url']->getOptions(), $data->options));
+        $element['url']->setOptions(NestedArray::mergeDeep($element['url']->getOptions(), $data->options));
       }
-      $element['#original_link'] = $link;
+      $element['original_link'] = $link;
       // Index using the link's unique ID.
-      $build[$link->getPluginId()] = $element;
+      $items[$link->getPluginId()] = $element;
     }
-    if ($build) {
+
+    if (!$items) {
+      return array();
+    }
+    elseif ($level == 0) {
+      $build = array();
       // Make sure drupal_render() does not re-order the links.
       $build['#sorted'] = TRUE;
       // Get the menu name from the last link.
       $menu_name = $link->getMenuName();
       // Add the theme wrapper for outer markup.
       // Allow menu-specific theme overrides.
-      $build['#theme_wrappers'][] = 'menu_tree__' . strtr($menu_name, '-', '_');
+      $build['#theme'] = 'menu__' . strtr($menu_name, '-', '_');
+      $build['#items'] = $items;
       // Set cache tag.
       $build['#cache']['tags']['menu'][$menu_name] = $menu_name;
+      return $build;
+    }
+    else {
+      return $items;
     }
-
-    return $build;
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Template/TwigExtension.php b/core/lib/Drupal/Core/Template/TwigExtension.php
index cf3500e..0294e18 100644
--- a/core/lib/Drupal/Core/Template/TwigExtension.php
+++ b/core/lib/Drupal/Core/Template/TwigExtension.php
@@ -13,6 +13,8 @@
 namespace Drupal\Core\Template;
 
 use Drupal\Core\Routing\UrlGeneratorInterface;
+use Drupal\Core\Url;
+use Drupal\Core\Utility\LinkGeneratorInterface;
 
 /**
  * A class providing Drupal Twig extensions.
@@ -31,6 +33,13 @@ class TwigExtension extends \Twig_Extension {
   protected $urlGenerator;
 
   /**
+   * The link generator.
+   *
+   * @var \Drupal\Core\Utility\LinkGeneratorInterface
+   */
+  protected $linkGenerator;
+
+  /**
    * Constructs \Drupal\Core\Template\TwigExtension.
    *
    * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
@@ -42,6 +51,19 @@ public function setGenerators(UrlGeneratorInterface $url_generator) {
   }
 
   /**
+   * Sets the link generator.
+   *
+   * @param \Drupal\Core\Utility\LinkGeneratorInterface $link_generator
+   *   The link generator.
+   *
+   * @return $this
+   */
+  public function setLinkGenerator(LinkGeneratorInterface $link_generator) {
+    $this->linkGenerator = $link_generator;
+    return $this;
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function getFunctions() {
@@ -53,6 +75,7 @@ public function getFunctions() {
       new \Twig_SimpleFunction('url', array($this, 'getUrl'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))),
       new \Twig_SimpleFunction('path', array($this, 'getPath'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))),
       new \Twig_SimpleFunction('url_from_path', array($this, 'getUrlFromPath'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))),
+      new \Twig_SimpleFunction('link_from_url', array($this, 'getLinkFromUrl')),
     );
   }
 
@@ -175,6 +198,27 @@ public function getUrlFromPath($path, $options = array()) {
   }
 
   /**
+   * Gets a rendered link from an url object.
+   *
+   * @param string $text
+   *   The link text for the anchor tag as a translated string.
+   * @param \Drupal\Core\Url|string $url
+   *   The URL object or string used for the link.
+   *
+   * @return string
+   *   An HTML string containing a link to the given url.
+   */
+  public function getLinkFromUrl($text, $url) {
+    if ($url instanceof Url) {
+      return $this->linkGenerator->generateFromUrl($text, $url);
+    }
+    else {
+      // @todo Convert once https://www.drupal.org/node/2306901 is in
+      return l($text, $url);
+    }
+  }
+
+  /**
    * Determines at compile time whether the generated URL will be safe.
    *
    * Saves the unneeded automatic escaping for performance reasons.
diff --git a/core/modules/system/src/Tests/Theme/EngineTwigTest.php b/core/modules/system/src/Tests/Theme/EngineTwigTest.php
index c03f16b..39ef521 100644
--- a/core/modules/system/src/Tests/Theme/EngineTwigTest.php
+++ b/core/modules/system/src/Tests/Theme/EngineTwigTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\system\Tests\Theme;
 
+use Drupal\Core\Url;
 use Drupal\simpletest\WebTestBase;
 
 /**
@@ -63,4 +64,24 @@ public function testTwigUrlGenerator() {
     }
   }
 
+  /**
+   * Tests the link_generator twig functions.
+   */
+  public function testTwigLinkGenerator() {
+    $this->drupalGet('twig-theme-test/link-generator');
+
+    $link_generator = $this->container->get('link_generator');
+
+    $expected = [
+      'link via the linkgenerator: ' . $link_generator->generateFromUrl('register', new Url('user.register')),
+    ];
+
+    $content = $this->drupalGetContent();
+    debug($content);
+    $this->assertFalse(empty($content), 'Page content is not empty');
+    foreach ($expected as $string) {
+      $this->assertRaw('<div>' . $string . '</div>');
+    }
+  }
+
 }
diff --git a/core/modules/system/templates/menu-tree.html.twig b/core/modules/system/templates/menu-tree.html.twig
deleted file mode 100644
index 03acca6..0000000
--- a/core/modules/system/templates/menu-tree.html.twig
+++ /dev/null
@@ -1,40 +0,0 @@
-{#
-/**
- * @file
- * Default theme implementation for a menu tree.
- *
- * Available variables:
- * - attributes: Attributes for the UL containing the tree of links.
- * - tree: Menu tree to be output.
- * - heading: (optional) A heading to precede the links.
- *   - text: The heading text.
- *   - level: The heading level (e.g. 'h2', 'h3').
- *   - attributes: (optional) A keyed list of attributes for the heading.
- *   If the heading is a string, it will be used as the text of the heading and
- *   the level will default to 'h2'.
- *
- *   Headings should be used on navigation menus and any list of links that
- *   consistently appears on multiple pages. To make the heading invisible use
- *   the 'visually-hidden' CSS class. Do not use 'display:none', which
- *   removes it from screen-readers and assistive technology. Headings allow
- *   screen-reader and keyboard only users to navigate to or skip the links.
- *   See http://juicystudio.com/article/screen-readers-display-none.php and
- *   http://www.w3.org/TR/WCAG-TECHS/H42.html for more information.
- *
- * @see template_preprocess_menu_tree()
- *
- * @ingroup themeable
- */
-#}
-{% if tree -%}
-  {%- if heading -%}
-    {%- if heading.level -%}
-      <{{ heading.level }}{{ heading.attributes }}>{{ heading.text }}</{{ heading.level }}>
-    {%- else -%}
-      <h2{{ heading.attributes }}>{{ heading.text }}</h2>
-    {%- endif -%}
-  {%- endif -%}
-  <ul{{ attributes }}>
-    {{ tree }}
-  </ul>
-{%- endif %}
diff --git a/core/modules/system/templates/menu.html.twig b/core/modules/system/templates/menu.html.twig
new file mode 100644
index 0000000..392263b
--- /dev/null
+++ b/core/modules/system/templates/menu.html.twig
@@ -0,0 +1,44 @@
+{#
+/**
+ * @file
+ * Default theme implementation to display a menu.
+ *
+ * Available variables:
+ * - menu_name: The machine name of the menu.
+ * - items: A nested list of menu items. Each menu item contains:
+ *   - attributes: HTML attributes for the menu item.
+ *   - below: The menu item child items.
+ *   - title: The menu link title.
+ *   - url: The menu link url, instance of \Drupal\Core\Url
+ *   - localized_options: Menu link localized options.
+ *
+ * @ingroup themeable
+ */
+#}
+{% import _self as menus %}
+{# @TODO Add a nav element around.#}
+
+{#We call a macro which calls itself to render the full tree.
+@see http://twig.sensiolabs.org/doc/tags/macro.html
+#}
+{{ menus.menu_links(items, attributes, 0) }}
+
+{% macro menu_links(items, attributes, menu_level) %}
+  {% import _self as menus %}
+  {% if items %}
+    {% if menu_level == 0 %}
+      <ul{{ attributes.addClass('menu') }}>
+    {% else %}
+      <ul class="menu">
+    {% endif %}
+      {% for item in items %}
+        <li{{ item.attributes }}>
+          {{ link_from_url(item.title, item.url) }}
+          {% if item.below %}
+            {{ menus.menu_links(item.below, attributes, menu_level + 1) }}
+          {% endif %}
+        </li>
+      {% endfor %}
+    </ul>
+  {% endif %}
+{% endmacro %}
diff --git a/core/modules/system/tests/modules/twig_theme_test/src/TwigThemeTestController.php b/core/modules/system/tests/modules/twig_theme_test/src/TwigThemeTestController.php
index d454e5f..0aed984 100644
--- a/core/modules/system/tests/modules/twig_theme_test/src/TwigThemeTestController.php
+++ b/core/modules/system/tests/modules/twig_theme_test/src/TwigThemeTestController.php
@@ -6,6 +6,7 @@
  */
 
 namespace Drupal\twig_theme_test;
+use Drupal\Core\Url;
 
 /**
  * Controller routines for Twig theme test routes.
@@ -37,4 +38,14 @@ public function urlGeneratorRender() {
     );
   }
 
+  /**
+   * Renders for testing link_generator functions in a Twig template.
+   */
+  public function linkGeneratorRender() {
+    return array(
+      '#theme' => 'twig_theme_test_link_generator',
+      '#test_url' => new Url('user.register')
+    );
+  }
+
 }
diff --git a/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.link_generator.html.twig b/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.link_generator.html.twig
new file mode 100644
index 0000000..e5bf772
--- /dev/null
+++ b/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.link_generator.html.twig
@@ -0,0 +1 @@
+<div>link via the linkgenerator: {{ link_from_url("register", test_url) }}</div>
diff --git a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.module b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.module
index c8db9d6..f023cb1 100644
--- a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.module
+++ b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.module
@@ -31,6 +31,10 @@ function twig_theme_test_theme($existing, $type, $theme, $path) {
     'variables' => array(),
     'template' => 'twig_theme_test.url_generator',
   );
+  $items['twig_theme_test_link_generator'] = array(
+    'variables' => array('test_url' => NULL),
+    'template' => 'twig_theme_test.link_generator',
+  );
   return $items;
 }
 
diff --git a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.routing.yml b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.routing.yml
index 75b9bbb..d631898 100644
--- a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.routing.yml
+++ b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.routing.yml
@@ -18,3 +18,10 @@ twig_theme_test_url_generator:
     _content: '\Drupal\twig_theme_test\TwigThemeTestController::urlGeneratorRender'
   requirements:
     _access: 'TRUE'
+
+twig_theme_test_link_generator:
+  path: '/twig-theme-test/link-generator'
+  defaults:
+    _content: '\Drupal\twig_theme_test\TwigThemeTestController::linkGeneratorRender'
+  requirements:
+    _access: 'TRUE'
