[Parameter] is the right tool when a parent passes data directly to its immediate child. But what about a theme setting that every component in the tree needs? Or a logged-in user object needed four levels deep? Passing it through every intermediate component as a [Parameter] is called prop-drilling, and it is the problem that cascading values exist to solve.
The Prop-Drilling Problem
Imagine a theme object that needs to reach StatusBadge, which is four levels deep:
@* Level 1: Page *@
<Dashboard Theme="currentTheme" />
@* Level 2: Dashboard — must declare and forward [Parameter] Theme *@
<EmployeeList Theme="Theme" />
@* Level 3: EmployeeList — must declare and forward [Parameter] Theme *@
<EmployeeCard Theme="Theme" />
@* Level 4: EmployeeCard — must declare and forward [Parameter] Theme *@
<StatusBadge Theme="Theme" />
@* Level 5: StatusBadge — finally uses it *@
Dashboard, EmployeeList, and EmployeeCard each declare a Theme parameter they never actually use — they exist only as pass-throughs. Any time the theme type changes, you update five files. Cascading values eliminate the middle layers entirely.
CascadingValue — Broadcasting to a Subtree
Wrap the root with <CascadingValue> and everything inside it can receive the value:
@* App.razor or the root page *@
<CascadingValue Value="currentTheme">
<Dashboard />
</CascadingValue>
@code {
private AppTheme currentTheme = new AppTheme { Name = "dark", PrimaryColor = "#6366f1" };
}
Dashboard, EmployeeList, EmployeeCard, and every other descendant in the subtree can now access currentTheme — without any of them declaring a forwarding parameter.
[CascadingParameter] — Receiving Without the Middle Layer Knowing
StatusBadge (the deepest component that actually needs the theme) just declares:
// StatusBadge.razor
[CascadingParameter] public AppTheme? Theme { get; set; }
private string BorderColor => Theme?.Name == "dark" ? "#6366f1" : "#0078d4";
No changes needed in Dashboard, EmployeeList, or EmployeeCard. They are no longer involved in the data flow at all.
Panel — The Middle Layer That Knows Nothing
@* EmployeeCard.razor — knows nothing about Theme *@
<div class="employee-card">
<h3>@Employee.Name</h3>
<StatusBadge IsActive="Employee.IsActive" />
</div>
@code {
[Parameter, EditorRequired] public Employee Employee { get; set; } = default!;
// No Theme parameter. It doesn't need one.
}
Named Cascades vs Type-Matched Cascades
By default, Blazor matches a cascading value to a [CascadingParameter] by type. If you have two cascading values of the same type, use a name to distinguish them:
<CascadingValue Name="PrimaryTheme" Value="primaryTheme">
<CascadingValue Name="SecondaryTheme" Value="secondaryTheme">
<ChildComponent />
</CascadingValue>
</CascadingValue>
// ChildComponent.razor
[CascadingParameter(Name = "PrimaryTheme")] public AppTheme Primary { get; set; } = default!;
[CascadingParameter(Name = "SecondaryTheme")] public AppTheme Secondary { get; set; } = default!;
Real-World Use Cases
- Theme / colour scheme — pass a theme object once; every component reads colours and font sizes from it.
- Authenticated user context — pass the current user down from a layout so any page or widget can display the user's name or check permissions.
- EditForm validation context — Blazor's own
EditFormuses a cascadedEditContextso thatValidationMessagecomponents anywhere in the form can access validation state without prop-drilling.
IsFixed="true" — Performance Optimisation
If the cascaded value never changes after the component is created, add IsFixed="true":
<CascadingValue Value="appConfig" IsFixed="true">
<Router AppAssembly="typeof(App).Assembly" />
</CascadingValue>
Blazor skips checking descendants for re-renders when the cascaded value changes — because it never does. This is a significant performance win for large trees with static cascades like app configuration or culture settings.
When NOT to Cascade
| Scenario | Right tool |
|---|---|
| Data passed from a parent directly to its immediate child | [Parameter] |
| State shared across unrelated components (e.g., a shopping cart total) | Injected service (scoped DI) |
| Event-driven communication from child to parent | EventCallback<T> |
| Global config that any component in the app needs | Cascading value or DI singleton |
Key Takeaways
- Prop-drilling — passing a parameter through components that don't use it — is the problem cascading values solve.
- Wrap a subtree with
<CascadingValue Value="...">to broadcast a value to all descendants. - Any descendant receives it by declaring
[CascadingParameter]— intermediate components need zero changes. - When two cascades have the same type, use
Nameto distinguish them. - Add
IsFixed="true"when the value never changes to skip unnecessary re-render checks across the subtree. - Cascading values are not a replacement for DI services — use services for state shared across unrelated component trees.