Feature - Navigation - Nested Descendants

Automatically generates a nested navigation tree from your site structure, showing section-level links for moving between pages

What It Does#

Automatically generates a nested navigation tree from your site structure

The Navigation - Nested Descendants feature creates a hierarchical navigation list by traversing the content tree from the root ancestor of the current page down through all its descendants. It requires no configuration — simply add it to a page and it builds the navigation automatically from your site structure.

You can see it in action right now in the left column of this page, showing all the pages under the Features section.

This feature is designed for the left sidebar of layouts like layout363, similar to documentation sites like Starlight.

Responsive Behaviour#

Different presentations for desktop and mobile screen sizes

On desktop screens, the navigation renders as a full tree view showing the current section and all its child pages. The current page is highlighted so users can see where they are in the hierarchy.

On mobile screens below the medium breakpoint, the tree view is hidden and replaced with a breadcrumb trail. This gives users their location context without taking up the vertical space that a full tree would require on a narrow screen.

This responsive switch is handled entirely in the view using Bootstrap's display utility classes: d-none d-md-block for the tree and d-block d-md-none for the breadcrumb.

Sticky Positioning#

Keeps the navigation visible while scrolling through longer content

When placed in a sidebar column of a multi-column layout, the navigation can be made sticky so it remains visible as the user scrolls through the main content area. This is controlled by the Sticky toggle in the feature's settings panel.

Sticky positioning works because the sidebar area is stretched to match the height of the tallest column in the CSS Grid row. The navigation sticks within that tall area, staying visible while the main content scrolls past.

The sticky offset is set using the --navbar-height CSS custom property, which ensures the navigation sits just below the site header rather than overlapping it.

Three Navigation Features#

Each navigation feature serves a different purpose and works best in a specific position

UmBootstrap provides three complementary navigation features. The Nested Descendants feature you see here navigates between pages — it shows the site tree structure and links to sibling and child pages in the current section.

The In-Page Navigation feature navigates within a single page by linking to feature blocks using fragment identifiers. It uses a Contentment Data List picker for editor control, renders as a sticky sidebar on desktop, and switches to an offcanvas bottom bar on mobile.

The Table of Contents feature also navigates within a page, but auto-generates its list from titled feature blocks in the same area. It sits inline with the content and collapses on smaller screens. No picker is needed — it discovers everything automatically.

Nested Descendants for left sidebar, In-Page Navigation for right sidebar, Table of Contents for inline.

No Configuration Required#

Add it to a page and it works immediately

Unlike most features in UmBootstrap, the Nested Descendants navigation has no content properties to configure. It uses the featureComponentNoConfiguration composition, which means the editor simply adds the block to an area and it generates the navigation automatically from the content tree.

The only configurable aspect is through the feature settings panel, where the editor can toggle sticky positioning on or off and optionally apply a background colour using the shared colour picker.

This zero-configuration approach makes it one of the simplest features to use. Every page in a section gets consistent navigation without editors needing to maintain links or update menus when pages are added, moved, or renamed.

featureNavigationDescendants.cshtml#

@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<Umbraco.Cms.Core.Models.Blocks.BlockGridItem>

@{
    IPublishedContent vThisPage = Umbraco.AssignedContentItem;
    int intCurLevel = vThisPage.Level;
    IPublishedContent thisSectionPage = vThisPage.Ancestor(2)!;
    if (vThisPage.Level == 2) { thisSectionPage = vThisPage; }
}

@{
    var enableSticky = Model.Settings?.Value<bool>("featureSettingsEnableSticky") ?? false;
}

@{ void RenderLi(IPublishedContent navItem) {
    <li data-level="[email protected]">
        <a href="@navItem.Url()" class="list-group-item-action list-group-item
            [email protected] @(navItem.IsAncestorOrSelf(vThisPage) ? "active" : "")">
            @navItem.Value("pageTitleShort")
        </a>
        @if (navItem.IsAncestorOrSelf(vThisPage) && navItem.Children().Any()) {
            <ul class="list-group nav">
                @foreach (var child in navItem.Children()) { RenderLi(child); }
            </ul>
        }
    </li>
} }

<nav class="card d-none d-md-block @(enableSticky ? "sticky-nav" : "")">
    <header class="card-header">
        <h2 class="card-title">
            <a href="@thisSectionPage.Url()" class="list-group-item-action list-group-item">
                @thisSectionPage.Value("pageTitleShort")
            </a>
        </h2>
    </header>
    <ul class="list-group list-group-flush nav">
        @foreach (var navItem in thisSectionPage.Children()) { RenderLi(navItem); }
    </ul>
</nav>

<nav aria-label="breadcrumb" class="d-block d-md-none">
    @{ var ancestors = vThisPage.Ancestors().OrderBy(a => a.Level).ToArray(); }
    @if (ancestors.Length > 0) {
        <ol class="breadcrumb">
            @foreach (var ancestor in ancestors) {
                <li class="breadcrumb-item"><a href="@ancestor.Url()">@ancestor.Value("pageTitleShort")</a></li>
            }
            <li class="breadcrumb-item active">@vThisPage.Value("pageTitleShort")</li>
        </ol>
    }
</nav>