Drupal - Drupal 8 - Taxonomy Menu with Hierarchy

I needed the same thing. Unfortunately there is nothing for it yet so I had to create something for time being. It is not pretty but it works so I don't care.

/**
 * Returns renderable array of taxonomy terms from Categories vocabulary in
 * hierarchical structure ready to be rendered as html list.
 *
 * @param int $parent
 *   The ID of the parent taxonomy term.
 *
 * @param int $max_depth
 *   The max depth up to which to look up children.
 *
 * @param string $route_name
 *   The name of the route to be used for link generation.
 *   Taxonomy term(ID) will be provided as route parameter.
 *
 * @return array
 */
function mymodule_categories_tree($parent = 0, $max_depth = NULL, $route_name = 'mymodule.category.view') {
  // Load terms
  $tree = \Drupal::entityManager()->getStorage('taxonomy_term')->loadTree('categories', $parent, $max_depth);

  // Make sure there are terms to work with.
  if (empty($tree)) {
    return [];
  }

  // Sort tree by depth so we can easily find out the deepest level
  uasort($tree, function($a, $b) {
    // Change objects to array
    return \Drupal\Component\Utility\SortArray::sortByKeyInt((array) $a, (array) $b, 'depth');
  });

  // Get the value of the deepest term
  $deepest = end($tree);
  $deepest = $deepest->depth;

  // Create a structured array
  $list = [
    $parent => [
      'items' => [],
      'depth' => -1
    ]
  ];
  foreach ($tree AS $term) {
    $list[$term->tid] = (array) $term;
  }

  // See if we're on a node page and if so open the menu
  // on the proper position.
  $node = \Drupal::request()->attributes->get('node');
  if ($node) {
    $categories = $node->get('categories')->getValue();
    // Go through each category to find out the least deep one.
    // That one will be the one we'll open.
    $open_category = $parent;
    foreach ($categories AS $target) {
      $tid = $target['target_id'];
      if ($list[$tid]['depth'] > $list[$open_category]['depth']) {
        $open_category = $tid;
      }
    }
  } else {
    // See if we're on a term page and set the corresponding item
    // as active so we don't have to rely on JS.
    $term = \Drupal::request()->attributes->get('taxonomy_term');
    if ($term) {
      $open_term = $term->id();
    }
  }

  for ($i = $deepest; $i >= 0; $i--) {
    foreach ($list AS $term) {
      if ($term['depth'] == $i) {
        $item = [
          '#type' => 'link',
          '#weight' => $term['weight'],
          '#title' => $term['name'],
          '#url' => Url::fromRoute($route_name, ['taxonomy_term' => $term['tid']]),
          '#options' => [
            'set_active_class' => TRUE
          ]
        ];
        // If we're on a node page and this category was chosen
        // as active, set the link's class.
        if (isset($open_category) && $open_category == $term['tid']) {
          $item['#attributes']['class'][] = 'active';
        }
        // If we're on term page, set the link's class to 'active'
        // and if this item is a parent, open it.
        if (isset($open_term) && $open_term == $term['tid']) {
          $item['#attributes']['class'][] = 'active';
          if (!empty($term['items'])) {
            $item['#wrapper_attributes']['class'][] = 'open';
          }
        }
        // If this item has children
        if (!empty($term['items'])) {
          $item['items'] = $term['items'];
          $item['#wrapper_attributes']['class'][] = 'parent';
          $item['#prefix'] = '<span></span>';
          // If any of the child items has 'active' class,
          // or is also a parent and has 'open' class
          // add the 'open' class to this wrapper too.
          foreach ($item['items'] AS $child) {
            if (
              isset($child['#attributes']['class']) && in_array('active', $child['#attributes']['class'])
              || isset($child['#wrapper_attributes']['class']) && in_array('open', $child['#wrapper_attributes']['class'])
            ) {
              $item['#wrapper_attributes']['class'][] = 'open';
              break;
            }
          }
        }
        foreach ($term['parents'] AS $pid) {
          $list[$pid]['items'][$term['tid']] = $item;
        }
        unset($list[$term['tid']]);
      }
    }
  }

  return [
    '#theme' => 'item_list',
    '#items' => $list[$parent]['items'],
    '#attributes' => [
      'class' => ['categories-tree']
    ],
    '#attached' => [
      'library' => [
        'mymodule/categories_tree'
      ]
    ]
  ];
}