[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 EditForm uses a cascaded EditContext so that ValidationMessage components 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 Name to 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.