Feature - Navigation - In Page

Adds an on-page navigation list linking to features on the current page with sticky positioning and scroll spy support

Introduction#

An overview of the Navigation - In Page feature and what it provides to content editors and site visitors

The Navigation - In Page feature creates a sticky sidebar navigation that automatically links to features on the current page. As the visitor scrolls through the content, Bootstrap ScrollSpy highlights the currently visible section in the navigation list, providing a clear sense of position within the page.

This is particularly useful on longer pages where content is broken into distinct sections. Rather than forcing visitors to scroll blindly, the in-page navigation gives them an overview of what the page contains and lets them jump directly to the section they need.

Only features that have a Feature Title set will appear in the navigation picker. Features without a title — such as standalone images, the navigation block itself, or the page title and description block — are automatically excluded from the picker. This keeps the navigation clean and focused on meaningful, titled content sections.

How It Differs from Table of Contents#

Editor-controlled navigation vs automatic generation

The In-Page Navigation and Table of Contents features both link to sections within a page, but they work very differently.

The In-Page Navigation is editor-driven: the content editor uses a Contentment picker to choose exactly which blocks appear in the navigation and in what order. The picker can select blocks from any area on the page, not just the area the navigation sits in. This gives editors full control over what appears and what doesn't.

The Table of Contents is automatic: it discovers all titled feature blocks in its own area only and lists them in the order they appear. There is no picker and no way to exclude individual blocks — if a block has a Feature Title, it appears in the TOC.

The In-Page Navigation is designed for the right sidebar as a sticky card on desktop and an offcanvas bottom bar on mobile. The Table of Contents is designed for the main content area as a sticky collapsed bar that expands on click.

Feature Title Filtering#

How the navigation picker decides which features to show and which to exclude

The navigation picker is powered by a custom Contentment DataSource that scans the current page for all feature blocks across every block grid on the page. It applies a straightforward rule: only features that have a Feature Title set are offered in the picker.

This design decision means that content-focused features like Rich Text Editors, FAQs, Tabs, and Code blocks naturally appear in the picker when they have titles, while utility features like standalone images, navigation blocks, and the page title block are excluded because they typically don't carry a Feature Title.

The beauty of this approach is that it gives content editors implicit control. If you want a feature to be available for in-page navigation, simply give it a Feature Title. If you want to exclude a feature that does have a title, you can either remove its title or simply deselect it in the navigation picker.

Human-Readable Fragment IDs#

Clickable anchor links with slugified URLs instead of raw GUIDs

Feature blocks are identified by human-readable fragment IDs based on their title — for example #introduction-a1b2 rather than a raw GUID. A 4-character GUID suffix prevents collisions when two blocks share the same title on the same page.

The slug is generated by the SlugHelper.ToSlug() static helper, which normalises the title to lowercase, replaces spaces with hyphens, strips special characters and diacritics, and appends the first 4 characters of the block's ContentKey.

Feature headings are clickable links that update the browser URL with the fragment identifier. A small superscript # appears on hover as a subtle hint. This makes it easy for visitors to copy and share direct links to specific sections.

Future Enhancements#

Planned improvements and additional options

The Navigation - In Page feature continues to evolve. Some of the enhancements we are considering include:

  • Per-feature allow toggle — A setting on individual feature blocks to explicitly control whether they should be available to in-page navigation, giving editors more granular control beyond the Feature Title rule
  • Show/hide untitled blocks — A toggle on the navigation settings to include or exclude blocks without a Feature Title from the picker
  • Native sharing — Integrating the Web Share API so visitors can share the current URL including the fragment identifier directly from the page
  • Element type exclusion — Code-level configuration to exclude certain feature types from ever appearing in the picker

featureNavigationInPage.cshtml#

@using Umbraco.Cms.Core.Models.Blocks
@using Umbraco.Cms.Core.Models.PublishedContent
@using Umbootstrap.Web.Helpers
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<BlockGridItem>

@{
    var selectedKeys = Model.Content
        .Value<IEnumerable<string>>("featurePropertyNavigationInPageItems");
    var currentPage = Umbraco.AssignedContentItem;
    var blockGrids = currentPage?.Properties
        .Select(p => p.GetValue() as BlockGridModel)
        .Where(g => g != null)
        .Cast<BlockGridModel>()
        .ToList() ?? new List<BlockGridModel>();
}

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

@if (selectedKeys != null && selectedKeys.Any() && blockGrids.Count > 0)
{
    var resolvedItems = new List<(string title, string slug)>();
    foreach (var key in selectedKeys)
    {
        if (Guid.TryParse(key, out var contentKey))
        {
            var block = FindFeatureBlock(blockGrids, contentKey);
            if (block != null)
            {
                var title = block.Content
                    .Value<string>("featurePropertyFeatureTitle");
                if (!string.IsNullOrWhiteSpace(title))
                {
                    var alias = block.Content.ContentType.Alias;
                    resolvedItems.Add((title,
                        SlugHelper.ToSlug(title, alias, contentKey)));
                }
            }
        }
    }
    // ... desktop card, mobile offcanvas, ScrollSpy
}