Replace the docsite header or footer
To replace the bundled DocSite header or footer (or inject head tags, append CSS, route additional @page components) without forking the template, populate the four slot seams on DocSiteOptions. The bundled layout, content pipeline, SPA navigation, and MonorailCSS wiring keep working. To replace the article content island itself, follow Hydrate a Razor component as a client island. To rearrange the layout shell fundamentally, read When is DocSite the right starting point? before deciding whether AddDocSite is still the right starting point.
Before you begin
- An existing Pennington site wired through
AddDocSite(...)(see Scaffold a documentation site with DocSite if not). - Edits made in the
DocSiteOptionsfactory passed toAddDocSite, not the DocSite source — forking the template is out of scope (see When is DocSite the right starting point?). - Awareness that
ExtraStylesis appended to the generated/styles.css, so rules added there ship alongside the MonorailCSS utility output rather than as a separate stylesheet. - Awareness that these seams are set at host-build time — changes require a restart, or
dotnet watchfor hot-reload.
For a working setup, see examples/DocSiteChromeOverridesExample. SiteChromeOverrides.cs returns a populated DocSiteOptions exercising all four seams, Components/ExtraHeadFragment.razor backs the head-slot fragment, and Components/ExtraPage.razor is the routed @page component showing that AdditionalRoutingAssemblies widened the router. Program.cs runs the DocSite end-to-end against those overrides.
Build the populated options
The whole code surface for this recipe lives in one factory method, so the four seams sit together on a single record initializer. The example sets SiteTitle and Description alongside the override seams, matching the shape produced by AddDocSite(() => SiteChromeOverrides.BuildDocSiteOptions()).
/// <summary>
/// A populated <see cref="DocSiteOptions"/> that exercises all four
/// slot seams the how-to covers: <see cref="DocSiteOptions.AdditionalHtmlHeadContent"/>,
/// <see cref="DocSiteOptions.ExtraStyles"/>,
/// <see cref="DocSiteOptions.HeaderContent"/> / <see cref="DocSiteOptions.FooterContent"/>
/// (the string-HTML slots), and
/// <see cref="DocSiteOptions.AdditionalRoutingAssemblies"/>.
/// </summary>
public static DocSiteOptions BuildDocSiteOptions() => new()
{
SiteTitle = "DocSite Chrome Overrides",
Description = "Running DocSite that exercises every override seam on DocSiteOptions.",
HeaderContent = """<span class="chrome-header" data-chrome-overrides="docsite-header">Chrome Overrides</span>""",
FooterContent = """<span class="chrome-footer" data-chrome-overrides="docsite-footer">(c) 2026 Pennington</span>""",
AdditionalHtmlHeadContent = BuildHtmlHeadContent(),
ExtraStyles = BuildExtraStyles(),
AdditionalRoutingAssemblies = BuildAdditionalRoutingAssemblies(),
Areas =
[
new ContentArea("Guides", "guides"),
],
};
Inject tags into <head> via AdditionalHtmlHeadContent
AdditionalHtmlHeadContent is a raw HTML string rendered inside every page's <head>, making it the right seam for meta tags, preconnect hints, analytics snippets, and font <link> elements that MonorailCSS does not know about. To author the fragment as a Razor component instead, render it with ToHtmlString() once at startup and pass the resulting string — the example pairs SiteChromeOverrides.BuildHtmlHeadContent with Components/ExtraHeadFragment.razor so both shapes sit side by side.
"""
<meta name="x-chrome-overrides-head" content="extra-head-fragment">
<link rel="preconnect" href="https://example.com">
"""
Append rules to the generated stylesheet via ExtraStyles
ExtraStyles is a CSS string concatenated onto the MonorailCSS-generated /styles.css, making it the right home for @font-face declarations, custom-property overrides, and any selector the utility-class scanner will not discover on its own. Keep this string small — anything expressible as MonorailCSS utilities in Razor markup gets picked up automatically by CssClassCollectorProcessor.
"""
.chrome-header { font-weight: 600; color: var(--color-primary-700); }
.chrome-footer { font-size: 0.875rem; color: var(--color-base-500); }
"""
Replace the header and footer with the string slots
HeaderContent and FooterContent are raw HTML strings the DocSite layout splices into the top bar and footer regions — they accept anything an HTML fragment can hold, from a branded logo wordmark to a compliance notice. Because they are strings, no AdditionalRoutingAssemblies entry is needed for them; for a component-authored fragment, render it to HTML at startup the same way as the head snippet above.
var options = new DocSiteOptions
{
HeaderContent = "<span class=\"chrome-header\">Extensibility Lab</span>",
FooterContent = "<span class=\"chrome-footer\">(c) 2026 Pennington</span>",
// ...
};
Route your own @page components via AdditionalRoutingAssemblies
The DocSite shell only discovers @page directives in its own assembly by default; adding the host assembly to AdditionalRoutingAssemblies makes any @page "/route" component in that assembly routable alongside the bundled pages. The example returns [typeof(SiteChromeOverrides).Assembly] so a Razor component like ExtraPage.razor sitting next to Program.cs gets picked up without any additional DI wiring.
[typeof(SiteChromeOverrides).Assembly]
Replace the content island through the islands system
To swap the article body itself — the island whose IslandName is "content" — follow Hydrate a Razor component as a client island and register an IIslandRenderer with IslandName => "content" to displace the shipped DocSiteArticleSlotRenderer. This seam lives in the islands system rather than DocSiteOptions, which is why it is a separate recipe.
Register the implementation
AddDocSite takes a Func<DocSiteOptions> factory, so the most direct wiring is to pass the helper as a method reference and keep the host file short. The example's Program.cs runs this exact shape end-to-end.
using DocSiteChromeOverridesExample;
using Pennington.DocSite;
var builder = WebApplication.CreateBuilder(args);
// Live wiring referenced from step 6 of
// how-to/extensibility/override-docsite-components. The factory is a
// method reference, so the helper in SiteChromeOverrides.cs owns the
// full DocSiteOptions shape and Program.cs stays short.
builder.Services.AddDocSite(SiteChromeOverrides.BuildDocSiteOptions);
var app = builder.Build();
app.UseDocSite();
await app.RunDocSiteAsync(args);
Result
The chrome on every page is replaced by the configured fragments. The header reads "Chrome Overrides" on the left (rendered as <span class="chrome-header" data-chrome-overrides="docsite-header">), the footer carries the matching copyright span, every <head> gains the <meta name="x-chrome-overrides-head"> tag and the https://example.com preconnect, and /styles.css ends with the appended .chrome-header / .chrome-footer rules. Any @page "/route" component in the host assembly (for example /extra) routes alongside the bundled DocSite pages.
Verify
- Run
dotnet runand view page source on/— expect the<meta name="x-chrome-overrides-head">tag inside<head>, yourHeaderContentandFooterContentmarkup in the layout, and the.chrome-headerrule inside/styles.css. - Navigate to a route defined by a Razor component in your app assembly (for example
/extra) and confirm it renders. A 404 here meansAdditionalRoutingAssembliesis not including the right assembly. - Run
dotnet run -- build outputand searchoutput/index.htmlfor your head fragment andoutput/styles.cssfor yourExtraStylesrules to confirm the overrides survive publish.
Other DocSite extension points
The four chrome seams above are the most common overrides. DocSite exposes three more on DocSiteOptions plus two through direct DI registration, and together they cover every extension that does not require forking the template.
| Seam | What it does |
|---|---|
DocSiteOptions.ConfigurePennington |
Callback against the underlying PenningtonOptions. Register extra markdown sources, islands, or highlighters without leaving the template — see Use multiple content sources and Hydrate a Razor component as a client island. |
DocSiteOptions.AdditionalRoutingAssemblies |
Widens the router to pick up @page components in your host assembly. Covered in the section above. |
DocSiteOptions.CustomCssFrameworkSettings |
Mutates the MonorailCSS CssFrameworkSettings after DocSite applies its theme. Use for custom palettes, variants, or plugins beyond what the defaults ship. |
services.AddSingleton<IContentService, T>() |
A concrete IContentService registered directly on the DI container is picked up by the pipeline alongside DocSite's own markdown service — see Source content from outside the file system. |
services.AddScoped<IIslandRenderer, T>() |
Scoped lifetime is required (the backing ComponentRenderer is scoped). See Hydrate a Razor component as a client island for the full recipe. |
Related
- Reference: Pennington.DocSite.DocSiteOptions
- Background: When is DocSite the right starting point?
- Related how-to: Hydrate a Razor component as a client island