Blazor Advanced Components Created: 04 Feb 2026 Updated: 04 Feb 2026

RenderFragment in Blazor: A Complete Guide

RenderFragment is one of the most powerful features in Blazor that enables component composition and flexible UI templating. It allows components to accept arbitrary UI content from their parent, making it possible to create highly reusable and customizable components.

In this comprehensive guide, we'll explore everything about RenderFragment – from basic concepts to advanced patterns like templated components, tab controls, wizards, and layout systems.

What is RenderFragment?

RenderFragment is a delegate type that represents a segment of UI content. Think of it as a "placeholder" where parent components can inject their own markup.

// Definition in Microsoft.AspNetCore.Components
public delegate void RenderFragment(RenderTreeBuilder builder);

Key Characteristics

  1. Delegate Type: It's a delegate that builds UI content
  2. Composable: Enables component composition patterns
  3. Flexible: Can contain any valid Razor markup
  4. Nullable: Can be optional with null checks

The Data Flow

┌─────────────────────────────────────────────────────────────┐
│ RENDERFRAGMENT FLOW │
├─────────────────────────────────────────────────────────────┤
│ │
│ Parent Component Child Component │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ <MyCard> │ │ [Parameter] │ │
│ │ <p>Content</p>│ ──────────────►│ RenderFragment │ │
│ │ </MyCard> │ Captures │ ChildContent │ │
│ └─────────────────┘ └────────┬────────┘ │
│ │ │
│ ▼ │
│ @ChildContent │
│ (renders content) │
└─────────────────────────────────────────────────────────────┘

Basic RenderFragment: ChildContent

The simplest and most common use of RenderFragment is ChildContent – the content placed between a component's opening and closing tags.

Creating a Component with ChildContent

@* SimpleCard.razor *@
<div class="card">
<div class="card-body">
@ChildContent
</div>
</div>

@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
}

Using the Component

<SimpleCard>
<h3>Card Title</h3>
<p>This content is passed to ChildContent automatically!</p>
<button class="btn btn-primary">Click Me</button>
</SimpleCard>

How It Works

  1. Content between <SimpleCard> tags is captured as a RenderFragment
  2. Blazor automatically assigns it to the ChildContent parameter
  3. The component renders it using @ChildContent

Note: ChildContent is a special name – content between component tags automatically maps to this parameter.

Named RenderFragments (Multiple Slots)

Components can have multiple RenderFragment parameters, each serving as a different "slot" for content.

Creating a Component with Multiple Slots

@* AdvancedCard.razor *@
<div class="card">
@if (HeaderContent != null)
{
<div class="card-header">
@HeaderContent
</div>
}
<div class="card-body">
@ChildContent
</div>
@if (FooterContent != null)
{
<div class="card-footer">
@FooterContent
</div>
}
</div>

@code {
[Parameter]
public RenderFragment? HeaderContent { get; set; }
[Parameter]
public RenderFragment? ChildContent { get; set; }
[Parameter]
public RenderFragment? FooterContent { get; set; }
}

Using Named RenderFragments

<AdvancedCard>
<HeaderContent>
<h5>Custom Header</h5>
</HeaderContent>
<ChildContent>
<p>Main body content goes here.</p>
</ChildContent>
<FooterContent>
<button class="btn btn-primary">Save</button>
<button class="btn btn-secondary">Cancel</button>
</FooterContent>
</AdvancedCard>

Implicit ChildContent

When mixing named and unnamed content, unnamed content goes to ChildContent:

<AdvancedCard>
<HeaderContent>Header</HeaderContent>
This text automatically goes to ChildContent
<FooterContent>Footer</FooterContent>
</AdvancedCard>

Null-Safe RenderFragment Handling

Always check for null before rendering optional RenderFragments:

@* NullSafeCard.razor *@
<div class="card">
@if (HeaderContent != null)
{
<div class="card-header">
@HeaderContent
</div>
}
<div class="card-body">
@if (ChildContent != null)
{
@ChildContent
}
else
{
<p class="text-muted">No content provided</p>
}
</div>
</div>

@code {
[Parameter]
public RenderFragment? HeaderContent { get; set; }
[Parameter]
public RenderFragment? ChildContent { get; set; }
}

Generic RenderFragment<T> - Templated Components

RenderFragment<T> is a generic delegate that accepts a parameter, enabling templated components where the parent defines how to render each item.

// Definition
public delegate RenderFragment RenderFragment<T>(T value);

Creating a Templated List Component

@* TemplatedList.razor *@
@typeparam TItem

<ul class="list-group">
@foreach (var item in Items)
{
<li class="list-group-item">
@ItemTemplate(item)
</li>
}
</ul>

@code {
[Parameter, EditorRequired]
public IEnumerable<TItem> Items { get; set; } = Enumerable.Empty<TItem>();
[Parameter, EditorRequired]
public RenderFragment<TItem> ItemTemplate { get; set; } = default!;
}

Using the Templated List

@* Simple strings *@
<TemplatedList Items="@fruits" Context="fruit">
<ItemTemplate>
<strong>@fruit</strong>
</ItemTemplate>
</TemplatedList>

@* Complex objects *@
<TemplatedList Items="@products" Context="product">
<ItemTemplate>
<div class="d-flex justify-content-between">
<span>@product.Name</span>
<span class="badge bg-info">$@product.Price</span>
</div>
</ItemTemplate>
</TemplatedList>

@code {
private List<string> fruits = new() { "Apple", "Banana", "Cherry" };
private List<Product> products = new()
{
new Product { Name = "Laptop", Price = 999.99m },
new Product { Name = "Mouse", Price = 29.99m }
};
}

The Context Attribute

The Context attribute defines the variable name for each item:

@* Default context name is "context" *@
<TemplatedList Items="@items">
<ItemTemplate>
@context.Name @* Uses default "context" *@
</ItemTemplate>
</TemplatedList>

@* Custom context name *@
<TemplatedList Items="@items" Context="item">
<ItemTemplate>
@item.Name @* Uses custom "item" *@
</ItemTemplate>
</TemplatedList>

Templated Table Component

A more complex example combining regular RenderFragment and generic RenderFragment<T>:

@* TemplatedTable.razor *@
@typeparam TItem

<table class="table">
<thead>
<tr>
@HeaderTemplate @* Regular RenderFragment *@
</tr>
</thead>
<tbody>
@foreach (var item in Items)
{
<tr>
@RowTemplate(item) @* Generic RenderFragment<TItem> *@
</tr>
}
</tbody>
</table>

@code {
[Parameter, EditorRequired]
public IEnumerable<TItem> Items { get; set; } = Enumerable.Empty<TItem>();
[Parameter, EditorRequired]
public RenderFragment HeaderTemplate { get; set; } = default!;
[Parameter, EditorRequired]
public RenderFragment<TItem> RowTemplate { get; set; } = default!;
}

Usage

<TemplatedTable Items="@users" Context="user">
<HeaderTemplate>
<th>ID</th>
<th>Name</th>
<th>Email</th>
</HeaderTemplate>
<RowTemplate>
<td>@user.Id</td>
<td>@user.Name</td>
<td>@user.Email</td>
</RowTemplate>
</TemplatedTable>

Rich Context Objects

Pass additional metadata beyond just the item:

@* IndexedList.razor *@
@typeparam TItem

<div>
@{
var itemList = Items.ToList();
for (int i = 0; i < itemList.Count; i++)
{
var context = new ItemContext<TItem>
{
Item = itemList[i],
Index = i,
IsFirst = i == 0,
IsLast = i == itemList.Count - 1
};
@ItemTemplate(context)
}
}
</div>

@code {
[Parameter, EditorRequired]
public IEnumerable<TItem> Items { get; set; } = Enumerable.Empty<TItem>();
[Parameter, EditorRequired]
public RenderFragment<ItemContext<TItem>> ItemTemplate { get; set; } = default!;
public class ItemContext<T>
{
public T Item { get; set; } = default!;
public int Index { get; set; }
public bool IsFirst { get; set; }
public bool IsLast { get; set; }
}
}

Usage with Rich Context

<IndexedList Items="@items">
<ItemTemplate Context="ctx">
<div class="@(ctx.IsEven ? "bg-light" : "")">
#@ctx.Index: @ctx.Item
@if (ctx.IsFirst) { <span class="badge bg-success">First</span> }
@if (ctx.IsLast) { <span class="badge bg-danger">Last</span> }
</div>
</ItemTemplate>
</IndexedList>

Loading/Empty State Templates

A common pattern for data-driven components:

@* DataContainer.razor *@
@typeparam TItem

<div>
@if (IsLoading)
{
@LoadingTemplate
}
else if (!Items.Any())
{
@EmptyTemplate
}
else
{
@foreach (var item in Items)
{
@ItemTemplate(item)
}
}
</div>

@code {
[Parameter]
public IEnumerable<TItem> Items { get; set; } = Enumerable.Empty<TItem>();
[Parameter]
public bool IsLoading { get; set; }
[Parameter, EditorRequired]
public RenderFragment<TItem> ItemTemplate { get; set; } = default!;
[Parameter]
public RenderFragment? LoadingTemplate { get; set; }
[Parameter]
public RenderFragment? EmptyTemplate { get; set; }
}

Usage

<DataContainer Items="@products" IsLoading="@isLoading" Context="product">
<ItemTemplate>
<ProductCard Product="@product" />
</ItemTemplate>
<LoadingTemplate>
<div class="spinner-border"></div>
<p>Loading...</p>
</LoadingTemplate>
<EmptyTemplate>
<p>No products found.</p>
<button @onclick="AddProduct">Add Product</button>
</EmptyTemplate>
</DataContainer>

Advanced Pattern: Parent-Child Component Communication

Tab Control with CascadingValue

TabContainer.razor (Parent)

<div class="tab-container">
<ul class="nav nav-tabs">
@foreach (var tab in tabs)
{
<li class="nav-item">
<button class="nav-link @(tab.Title == ActiveTab ? "active" : "")"
@onclick="@(() => SelectTab(tab.Title))">
@tab.Title
</button>
</li>
}
</ul>
<div class="tab-content p-3">
@foreach (var tab in tabs)
{
if (tab.Title == ActiveTab)
{
@tab.ChildContent
}
}
</div>
<CascadingValue Value="this">
<div style="display: none;">
@ChildContent
</div>
</CascadingValue>
</div>

@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
[Parameter]
public string ActiveTab { get; set; } = "";
[Parameter]
public EventCallback<string> ActiveTabChanged { get; set; }
private List<Tab> tabs = new();
internal void AddTab(Tab tab)
{
tabs.Add(tab);
if (string.IsNullOrEmpty(ActiveTab))
ActiveTab = tab.Title;
StateHasChanged();
}
private async Task SelectTab(string title)
{
ActiveTab = title;
await ActiveTabChanged.InvokeAsync(title);
}
}

Tab.razor (Child)

@implements IDisposable

@code {
[CascadingParameter]
public TabContainer? Parent { get; set; }
[Parameter, EditorRequired]
public string Title { get; set; } = "";
[Parameter]
public RenderFragment? ChildContent { get; set; }
protected override void OnInitialized()
{
Parent?.AddTab(this);
}
public void Dispose()
{
// Cleanup when tab is removed
}
}

Usage

<TabContainer @bind-ActiveTab="activeTab">
<Tab Title="Home">
<h3>Home Content</h3>
<p>Welcome to the home tab!</p>
</Tab>
<Tab Title="Profile">
<h3>Profile Content</h3>
<p>Your profile information.</p>
</Tab>
<Tab Title="Settings">
<h3>Settings Content</h3>
<p>Application settings.</p>
</Tab>
</TabContainer>

@code {
private string activeTab = "Home";
}

Layout Components with Multiple Slots

Create reusable layout structures:

@* DashboardLayout.razor *@
<div class="dashboard">
<aside class="sidebar">
@Sidebar
</aside>
<header class="header">
@Header
</header>
<main class="main-content">
@MainContent
</main>
@if (Footer != null)
{
<footer class="footer">
@Footer
</footer>
}
</div>

@code {
[Parameter]
public RenderFragment? Sidebar { get; set; }
[Parameter]
public RenderFragment? Header { get; set; }
[Parameter]
public RenderFragment? MainContent { get; set; }
[Parameter]
public RenderFragment? Footer { get; set; }
}

Usage

<DashboardLayout>
<Sidebar>
<nav>
<a href="/">Home</a>
<a href="/users">Users</a>
</nav>
</Sidebar>
<Header>
<h1>Dashboard</h1>
</Header>
<MainContent>
<div class="row">
<div class="col">Statistics here...</div>
</div>
</MainContent>
<Footer>
<p>© 2024 My App</p>
</Footer>
</DashboardLayout>

Dynamic RenderFragment Creation

Create RenderFragments programmatically:

Method 1: Inline Razor Syntax

@code {
private RenderFragment GetContent() => @<p>Dynamic content!</p>;
private RenderFragment GetConditionalContent(bool showDetails) => showDetails
? @<div class="details">Detailed view</div>
: @<div class="summary">Summary view</div>;
}

Method 2: RenderTreeBuilder

@code {
private RenderFragment dynamicFragment;
private void GenerateFragment()
{
dynamicFragment = builder =>
{
builder.OpenElement(0, "div");
builder.AddAttribute(1, "class", "dynamic-content");
builder.OpenElement(2, "h3");
builder.AddContent(3, "Generated at: " + DateTime.Now);
builder.CloseElement();
builder.CloseElement();
};
}
}

Method 3: From Component Type

@code {
private RenderFragment RenderComponent<TComponent>() where TComponent : IComponent
{
return builder =>
{
builder.OpenComponent<TComponent>(0);
builder.CloseComponent();
};
}
private RenderFragment RenderComponentWithParams<TComponent>(
Dictionary<string, object> parameters) where TComponent : IComponent
{
return builder =>
{
builder.OpenComponent<TComponent>(0);
builder.AddMultipleAttributes(1, parameters);
builder.CloseComponent();
};
}
}
Share this lesson: