How it works
Umbraco pages form a tree. Your menu is usually the children of the home page, and breadcrumbs are the ancestors of the current page. Both are a few lines of Razor.
Step 1: Let editors hide pages from the menu
Add a True/false property with the alias umbracoNaviHide to your document types, or better, to a shared composition. Umbraco's IsVisible() helper automatically skips any page where it's ticked.
Step 2: The menu partial
Create Views/Partials/Navigation.cshtml:
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage
@{
var home = Model.Root();
var menu = home.Children().Where(x => x.IsVisible());
}
<nav aria-label="Main">
<ul>
<li><a href="@home.Url()" class="@(Model.Id == home.Id ? "active" : null)">@home.Name</a></li>
@foreach (var item in menu)
{
var isActive = Model.Path.Split(',').Contains(item.Id.ToString());
<li>
<a href="@item.Url()" class="@(isActive ? "active" : null)" aria-current="@(Model.Id == item.Id ? "page" : null)">@item.Name</a>
@if (item.Children().Any(x => x.IsVisible()))
{
<ul>
@foreach (var child in item.Children().Where(x => x.IsVisible()))
{
<li><a href="@child.Url()">@child.Name</a></li>
}
</ul>
}
</li>
}
</ul>
</nav>Render it from your layout with @await Html.PartialAsync("Navigation").
Model.Pathis a comma-separated list of the current page's ancestor IDs, so the check highlights a section when you're on any page inside it.aria-current="page"tells screen readers which page you're on.
Step 3: Breadcrumbs
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage
@{
var trail = Model.AncestorsOrSelf().Reverse().ToList();
}
@if (trail.Count > 1)
{
<nav aria-label="Breadcrumb">
<ol class="breadcrumbs">
@foreach (var item in trail)
{
if (item.Id == Model.Id)
{
<li aria-current="page">@item.Name</li>
}
else
{
<li><a href="@item.Url()">@item.Name</a></li>
}
}
</ol>
</nav>
}Step 4: Style the breadcrumb separators
.breadcrumbs { display: flex; flex-wrap: wrap; gap: .5rem; list-style: none; padding: 0; }
.breadcrumbs li + li::before { content: "›"; margin-right: .5rem; opacity: .6; }Tips
- Different menu label: add a Textstring such as
navigationTitleand useitem.Value<string>("navigationTitle") ?? item.Name. - Performance: menus are read from Umbraco's in-memory cache, so they're fast. If you build a very large mega-menu, wrap it in Razor's
<cache>tag helper.
Need a hand?
umbraco.ninja has more than 20 years of Umbraco experience. If you'd rather not do this yourself, we'll set it up, test it and hand it over.
Get a fixed-price quote