Layouts

A layout wraps the routed page with shared chrome — headers, navigation, footers. BlazorCodeFirst layouts are written the same way as components: derive from ChromeLayoutBase, declare a design-time UI expression, and let the source generator produce the rendering.

Chrome and Body#

ChromeLayoutBase derives from Blazor's LayoutComponentBase, so it already has a Body parameter holding the routed page. The chrome the layout itself draws goes in a separate overridden property, Chrome:

using BlazorCodeFirst;
using static BlazorCodeFirst.Html;

public partial class MainLayout : ChromeLayoutBase
{
    protected override View Chrome =>
        Div.Class("shell")[
            Header[H1["My App"]],
            Main.Class("content")[Body],
            Footer["© 2026"]];
}

Main[Body] here is exactly <main>@Body</main> in Razor — the routed page dropped in as element content.

Why Chrome, not Body#

Blazor requires a layout's routed content to be exposed through a parameter named exactly Body, and C# cannot declare two members with the same name on one type. So Body keeps its Razor meaning — the page being wrapped — and the layout's own design-time expression is named Chrome instead.

Nesting layouts#

A layout can itself sit inside another layout. Put [Layout] on the layout type, exactly as you would on a page:

using BlazorCodeFirst;
using Microsoft.AspNetCore.Components;
using static BlazorCodeFirst.Html;

[Layout(typeof(SiteLayout))]
public partial class DocsLayout : ChromeLayoutBase
{
    protected override View Chrome => Div.Class("docs")[Aside[TableOfContents()], Main[Body]];
}

Nesting is resolved by Blazor, not by BlazorCodeFirst: LayoutView reads the attribute off the layout type and wraps it in its own layout, and a BlazorCodeFirst layout is an ordinary LayoutComponentBase descendant. Each level's Body holds the level below it — SiteLayout's Body is the rendered DocsLayout, whose own Body is the routed page.

RenderFragment becomes content directly#

Body is a plain Blazor RenderFragment?, not a BlazorCodeFirst type, yet Main[Body] above compiles without dedicated syntax: View has an implicit conversion from RenderFragment?, so any fragment can appear wherever element content is expected. The conversion is from the non-generic RenderFragment only — a RenderFragment<T> does not convert. And like Fragment and Raw, a RenderFragment opens no keyable frame, so it cannot be a ForEach content root and cannot carry decorations.

The same mechanism lets a BlazorCodeFirst component render children passed in from Razor. A component with [Parameter] public RenderFragment? ChildContent uses it exactly like Body:

using BlazorCodeFirst;
using Microsoft.AspNetCore.Components;
using static BlazorCodeFirst.Html;

public partial class Card : BodyComponentBase
{
    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    protected override View Body => Div.Class("card")[ChildContent];
}

Passing child content to components#

The direction above — Razor passing content into a BlazorCodeFirst component — uses the implicit RenderFragment? conversion. The opposite direction — BlazorCodeFirst code passing content to a Razor or hand-written Blazor component — uses Component<T>() with nested children or the .Param overload for RenderFragment parameters.

Nested children bind to ChildContent, mirroring Razor's rule that nested content becomes ChildContent and nothing else:

protected override View Body =>
    Component<Card>()[
        H2["Heading"],
        P["Body text"]];

This requires Card to have a settable [Parameter] public RenderFragment? ChildContent; otherwise BCF3013 is reported. A RenderFragment<TContext> parameter cannot receive the children — the generated lambda is non-generic and would fail an invalid cast at runtime.

Other RenderFragment parameters (such as Footer or Header) bind through .Param(c => c.Footer, content), naming the parameter explicitly:

protected override View Body =>
    Component<Card>()
        .Param(c => c.Title, "Card title")
        .Param(c => c.Footer, Span["Footer note"])[
            H2["Heading"],
            P["Body text"]];

It is also legal to name ChildContent through .Param — this is verbose but matches Razor's attribute form (<Card><ChildContent>...</ChildContent></Card>). Binding the same parameter through both channels reports BCF3007.

A real RenderFragment value (as opposed to a BlazorCodeFirst View expression) still binds through the generic .Param<TValue> overload and is emitted verbatim.

For unresolved type names inside parameter values, see Values copied into generated code.

Reads are allowed, mutation is not#

Both Chrome and Body (the BodyComponentBase one, not the layout's routed-content parameter) may read component state — projecting state to UI is their whole purpose — but neither may mutate it. Mutating state inside either reports BCF3001, the same diagnostic that applies to a regular component's Body.

Next#

See elements and decorations for the element vocabulary used above, or control flow for If and keyed ForEach.

An unhandled error has occurred. Reload 🗙