When a page loads, the browser automatically scrolls to the element whose id matches the fragment in the URL. The same happens when you click an anchor with an href like #element-id.
For example, loading https://example.com/#header will scroll the element with id header into view. Chromium-based browsers also support text fragments, which highlight a specific part of the page. The URL for this looks like https://example.com/#element:~:text=demo.
The second behavior occurs when you click a link that only changes the fragment: the browser scrolls to the matching element:
HTML
<a href="#header">#example</a>
#Why anchor navigation doesn't work in a Blazor application
The page load behavior does not work in a Blazor application unless pre-rendering is enabled.
- In a Blazor Server application, the SignalR connection must be established and data fetched from the server before rendering. By the time the browser looks for the element by id on page load, the element does not yet exist.
- In a Blazor WebAssembly application, the .NET runtime and application DLLs must load before any rendering occurs. As with Blazor Server, the page content is not available at page load.
Anchor click navigation also does not work by default, because Blazor intercepts navigation events for its own routing. This disables the browser's native anchor scroll behavior.
#Using a component to simulate anchor navigation
As usual in Blazor, the solution is to create a component.
The component handles the initial page load in the OnAfterRenderAsync method and listens for anchor clicks via the NavigationManager.LocationChanged event. In both cases, the current URI is read from NavigationManager.Uri, the fragment is parsed to extract the element id, and a JavaScript function scrolls that element into view.
Let's create a file named AnchorNavigation.razor with the following content:
Razor
@inject IJSRuntime JSRuntime
@inject NavigationManager NavigationManager
@implements IDisposable
@code {
protected override void OnInitialized()
{
NavigationManager.LocationChanged += OnLocationChanged;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await ScrollToFragment();
}
public void Dispose()
{
NavigationManager.LocationChanged -= OnLocationChanged;
}
private async void OnLocationChanged(object sender, LocationChangedEventArgs e)
{
await ScrollToFragment();
}
private async Task ScrollToFragment()
{
var uri = new Uri(NavigationManager.Uri, UriKind.Absolute);
var fragment = uri.Fragment;
if (fragment.StartsWith('#'))
{
// Handle text fragment (https://example.org/#test:~:text=foo)
// https://github.com/WICG/scroll-to-text-fragment/
var elementId = fragment.Substring(1);
var index = elementId.IndexOf(":~:", StringComparison.Ordinal);
if (index > 0)
{
elementId = elementId.Substring(0, index);
}
if (!string.IsNullOrEmpty(elementId))
{
await JSRuntime.InvokeVoidAsync("BlazorScrollToId", elementId);
}
}
}
}
Add the JavaScript function somewhere before the Blazor script:
HTML
<script>
function BlazorScrollToId(id) {
const element = document.getElementById(id);
if (element instanceof HTMLElement) {
element.scrollIntoView({
behavior: "smooth",
block: "start",
inline: "nearest"
});
}
}
</script>
#Demo
Add the component to the page. The demo below uses the LoremNET library to generate paragraphs and simulate a long page.
Razor
@page "/"
<strong>Table of Contents</strong>
<ul>
@for (int i = 0; i < 10; i++)
{
<li><a href="@GetHref(i)">Header @i</a></li>
}
</ul>
@for (int i = 0; i < 10; i++)
{
<h1 id="@GetId(i)">Header @i</h1>
@LoremNET.Lorem.Paragraph(wordCount: 30, sentenceCount: 10)
}
@* 👇 Integrate the component *@
<AnchorNavigation />
@code{
string GetId(int i) => "header-" + i;
string GetHref(int i) => "#" + GetId(i);
}
To prevent content from being hidden under a fixed header, use the scroll-margin-top CSS property (documentation).
CSS
/* Use a more specific selector if possible */
*[id] {
scroll-margin-top: 5rem;
}
#Conclusion
Creating a component keeps the solution clean and reusable. Anchor navigation is a simple but effective way to guide users to the right section of a page. If you would like this feature built into Blazor, consider upvoting the following GitHub issues:
Do you have a question or a suggestion about this post? Contact me!