ContentView
ContentView renders one content item as an article: a header with the title, author, date, reading time, tags and
featured image, the sanitized HTML body, and a footer with categories, tags and optional previous and next links. Use
it when you build your own page around a single item.
Package Osirion.Blazor.Cms.Web, namespace Osirion.Blazor.Cms.Web.Components. Source on GitHub.
Osirion.Blazor v1.5: Release Notes

Version 1.5.0 was released on 21 April 2025. This post is kept for history; the current release is 4.0.0, described in Osirion.Blazor 4.0: security hardening and Fluent UI v5.
Added
- A fluent API for service registration through the
AddOsirionBlazormethod, with theIOsirionBlazorBuilderinterface. - CSS framework integration for Bootstrap, Fluent UI, MudBlazor and Radzen: the
CssFrameworkenum, classes that map Osirion's CSS variables to each framework's variables, and aFrameworkIntegrationparameter onOsirionStyles. OsirionStyleOptions, a class of its own for styling options.
Changed
osirion-cms.csswas renamed toosirion.css, because it styles every component, not only the CMS ones.- Styling options moved from
GitHubCmsOptionstoOsirionStyleOptions. - The service collection extensions were reorganized. The earlier methods kept working.
Upgrading to 1.5
Update the stylesheet link to osirion.css and move styling settings from GitHubCmsOptions to
OsirionStyleOptions. The 1.5 section of the migration guide
has the details.
These names belong to 1.5. Later versions changed the registration API (AddOsirion) and the theming options, so
follow the current documentation for new projects.
@using Osirion.Blazor.Cms.Domain.Entities
@using Osirion.Blazor.Cms.Domain.Enums
@using Osirion.Blazor.Cms.Domain.Repositories
@using Osirion.Blazor.Cms.Domain.Services
@using Osirion.Blazor.Cms.Web.Components
@inject IContentProviderManager ContentProviders
@* The item is a page of this site's own content. The previous and next items come from the same folder, newest first,
and the formatters point the links at this site's routes: the defaults build links from the source path. *@
@if (item is not null)
{
<div style="max-height: 28rem; overflow: auto; padding: 1rem; border: 1px solid var(--osirion-border-color, #d1d5db); border-radius: 0.5rem;"
tabindex="0" role="region" aria-label="The release notes page in a ContentView">
<ContentView Item="@item"
HeadingLevel="2"
PreviousItem="@previous"
NextItem="@next"
ShowNavigationLinks="true"
ContentUrlFormatter="@(neighbor => "/" + neighbor.Url)"
CategoryUrlFormatter="@(category => "/category/" + Slug(category))"
TagUrlFormatter="@(tag => "/tag/" + Slug(tag))" />
</div>
}
else
{
<p>The sample page was not found in the content provider.</p>
}
@code {
private ContentItem? item;
private ContentItem? previous;
private ContentItem? next;
protected override async Task OnInitializedAsync()
{
var provider = ContentProviders.GetDefaultProvider();
if (provider is null)
{
return;
}
var current = await provider.GetItemByUrlAsync("blog/osirion-blazor-v1-5-release-notes");
item = current;
if (current is null)
{
return;
}
var siblings = await provider.GetItemsByQueryAsync(new ContentQuery
{
Directory = current.Directory?.Path,
Locale = current.Locale,
SortBy = SortField.Date,
SortDirection = SortDirection.Descending
}) ?? [];
var index = siblings.ToList().FindIndex(sibling => sibling.Path == current.Path);
previous = index > 0 ? siblings[index - 1] : null;
next = index >= 0 && index < siblings.Count - 1 ? siblings[index + 1] : null;
}
private static string Slug(string name) => Uri.EscapeDataString(name.Trim().ToLowerInvariant().Replace(' ', '-'));
}The demo loads this site's own page for the 1.5 release notes and shows it in a scrolling frame. The neighbors come
from the same folder, newest first, and the formatters point the links at this site's routes. ContentView writes
the title as an h1 unless you set HeadingLevel; the demo sets it to 2, because this documentation page has
its own h1.
Usage
Pass an item you already loaded:
@using Osirion.Blazor.Cms.Web.Components
<ContentView Item="@item" />
Or let the component load it by the item's source path (ContentItem.Path, as the provider reports it):
<ContentView Path="blog/getting-started.md"
ShowNavigationLinks="true"
ContentUrlFormatter="@(item => "/" + item.Url)"
TagUrlFormatter="@(tag => $"/blog/tag/{Uri.EscapeDataString(tag)}")"
AuthorUrlFormatter="@(name => $"/authors/{Uri.EscapeDataString(name.ToLowerInvariant().Replace(' ', '-'))}")" />
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
Path |
string |
"" |
The source path of the item to load from the default provider (ContentItem.Path, for example blog/getting-started.md) while Item is not set. A null or white-space path loads nothing. |
LoadingText |
string |
"Loading content..." |
The text shown while the item loads, in interactive render modes only. A statically rendered response is sent once the item has loaded. |
NotFoundText |
string |
"Content not found." |
The message of the not-found block, shown under a fixed Content Not Found heading and above a link back to /. The block appears when no item is found, when loading fails (the error is logged) and when neither Item nor Path is set. |
CategoryUrlFormatter |
Func<string, string>? |
null |
Builds a category link from the category name. Without it the link is /category/{slug}. |
TagUrlFormatter |
Func<string, string>? |
null |
Builds a tag link from the tag name. Without it the link is /tag/{slug}. |
AuthorUrlFormatter |
Func<string, string>? |
null |
How the author's page URL is built from the author's name. When set, the author's name links there with rel="author". |
ContentUrlFormatter |
Func<ContentItem, string>? |
null |
Builds the links of the previous and next items from the item. Without it a link is /{item.Path}, the source path, which is usually not the public address, so set it whenever navigation links are shown. |
Item |
ContentItem? |
null |
The item to render: header, sanitized body, categories, tags and navigation. An item the parent sets is never replaced by loading by Path. |
PreviousItem |
ContentItem? |
null |
The item the previous link points to. Loading by Path with ShowNavigationLinks on fills it, and clears what it filled when the path changes; a value you set is left alone until then. |
NextItem |
ContentItem? |
null |
The item the next link points to. See PreviousItem. |
ShowNavigationLinks |
bool |
false |
Whether the footer shows previous and next links. When the component loads by Path it also looks the neighbors up with a directory query built from the folder part of the path, newest first. For an item you pass in Item, set PreviousItem and NextItem yourself. |
HeadingLevel |
int |
1 |
The level of the heading that holds the title, 1 to 6 (h1 to h6). It defaults to 1, the page's own heading; set 2 for a view embedded in a page that has its own h1, so the page keeps a single one. A value outside the range is moved to the nearest level. The title keeps its look at every level. |
Theme |
ThemeMode |
ThemeMode.System |
The component's color mode. Light or Dark writes it to data-theme (data-bs-theme under Bootstrap) on the root element; System writes nothing. |
Class |
string? |
null |
Extra CSS classes for the component's root element. |
Style |
string? |
null |
An inline style for the component's root element. |
AuthorUrlFormatter is available from 4.1. The Theme and Style rows come from the base class. ContentView does not write them, or unmatched attributes, so only Class reaches the root element.
The default previous and next link is the item's source path, which usually differs from its public URL. When you
show navigation links, set ContentUrlFormatter, for example item => "/" + item.Url.
Behavior
- Rendering. Everything renders on the server under static SSR; there is no script. The loading text appears only
in interactive render modes. When
Pathchanges, the previous item and the neighbors the component loaded are cleared before the new one loads, so an old article never shows under a new address. - Not found. Without an item the component renders a "Content Not Found" heading,
NotFoundTextand a link back to/. The heading and the link text are fixed. The component also signals 404, asArticlePagedoes:NavigationManager.NotFound()on .NET 10, the status code of the request on .NET 9, while the response has not started. A view you embed in a page that must stay at 200 without its item should not be rendered without one. - Security. The body is written as HTML after it passes through the registered
IHtmlContentSanitizer(the default allow-list when none is registered), even when the item comes from a custom provider or from your code. Script, event handler attributes,javascript:,vbscript:anddata:URLs, forms, iframes, inline SVG and inline styles (apart from table cell alignment) are removed. The result is cached until the content changes. To allow a custom element, register it withAddOsirionHtmlSanitizer(options => options.AllowElement("chart", "type", "data")). Title, author and tags are encoded as text. - Headings. The title is an
h1by default, the page's own heading. SetHeadingLevel(1 to 6) to write another level,2for a view embedded in a page that has its ownh1; the title keeps its look. The body's own leadingh1(the# Titleline of the Markdown) is left out, so the title appears once; a top-level heading further down is kept. - Images. The featured image takes its
alttext from thefeatured_image_altentry of the front matter'scustom_fields, for examplecustom_fields:withfeatured_image_alt: A bridge at duskbelow it. The front matter model has no property of its own for it, and afeatured_image_altkey outsidecustom_fieldsis ignored. Without the entry thealtis empty, which marks the image as decorative, instead of repeating the title that the heading beside it already shows. From 4.0 the image loads eagerly withfetchpriority="high"and async decoding, because it is usually the largest element on an article page. - Dates. The date is
DateCreated(thedatefront matter), formatted asMMM d, yyyy. - SEO.
ContentViewwrites nothing to the page head. Render a SeoMetadataRenderer inside<HeadContent>next to it.
Styling
The root element has the class osirion-content-view; the article inside is osirion-content-article. The header
uses osirion-content-header, osirion-content-title and osirion-content-meta (items osirion-content-author,
osirion-content-date, osirion-content-read-time), the body osirion-content-body, and the footer
osirion-content-footer, osirion-content-category, osirion-content-tag and osirion-content-navigation
(osirion-content-previous, osirion-content-next). Tags are rendered twice, in the header
(osirion-content-tags-inline) and in the footer (osirion-content-tags-mobile), and the library styles neither
class, so both lists show at every width; hide one in your own CSS if you want only one.
The body is markup the component writes itself, outside OsirionHtmlRenderer, so the library gives its headings,
paragraphs, lists and tables no typography: they take the style of your CSS framework or your own CSS (the rules
osirion-content-body h2, p and the like that older builds carried never matched and are gone). Two rules do reach
it, and keep a wide image or code block inside the column: img is at most as wide as the body, and pre scrolls
sideways. For a body with the library's typography, render it with ContentRenderer.
Colors and spacing
come from theme tokens such as --osirion-text-primary, --osirion-text-secondary, --osirion-border-color and
--osirion-background-secondary.
Related
- CMS pages: the group overview
- LocalizedContentView: the same with translation links
- ContentRenderer: the body only, with code highlighting
- ContentPage: a full page layout around an item
- ContentList: lists, including
Authorfor author pages - OsirionAuthorCard: the author introduction on an author page