/coordinate-components
Share state between components that don't have a direct parent-child parameter relationship, using cascading values, scoped services with change events, or CascadingValueSource via DI. USE WHEN the user needs a CascadingParameter or CascadingValue that works across render mode
Claude Code
$ npx -y skills add dotnet/skills --skill coordinate-components --agent claude-code
Codex
$ npx skills add dotnet/skills --skill coordinate-components -a codex -y
Cursor
$ npx skills add dotnet/skills --skill coordinate-components -a cursor -y
Gemini CLI
$ npx skills add dotnet/skills --skill coordinate-components -a gemini-cli -y
GitHub Copilot
$ npx skills add dotnet/skills --skill coordinate-components -a github-copilot -y
OpenCode
$ npx skills add dotnet/skills --skill coordinate-components -a opencode -y
Windsurf
$ npx skills add dotnet/skills --skill coordinate-components -a windsurf -y
SKILL.md
coordinate-components.SKILL.mdlicense: MIT
name: coordinate-components
description: >
Share state between components that don't have a direct parent-child parameter relationship,
using cascading values, scoped services with change events, or CascadingValueSource via DI.
USE WHEN the user needs a CascadingParameter or CascadingValue that works across render mode
boundaries, a shopping cart or notification count accessible from multiple pages, a theme or
user preference cascaded app-wide, or when components in different parts of the tree must
react when shared data changes. Also USE WHEN cascading values aren't reaching interactive
children in per-page interactivity mode, or when the user needs to understand scoped vs
singleton service lifetime for state on Blazor Server.
DO NOT USE for direct parent-child parameter passing or EventCallback (see author-component),
for persisting state across prerender-to-interactive transitions (see support-prerendering),
or for service abstractions for data fetching in Auto/WebAssembly (see fetch-and-send-data).
Coordinate Components
Step 1 — Read AGENTS.md
Read `AGENTS.md` at the workspace root to learn the project's conventions before making changes.
Step 2 — Decide the scope
| Need | Mechanism | When to use | |------|-----------|-------------| | Subtree (same render mode) | `CascadingValue` component | Theme, layout config within a layout | | App-wide (all render modes) | `CascadingValueSource<T>` via DI | Current user, feature flags, theme shared globally | | Mutable shared state within a circuit | Scoped service + `Action` event | Shopping cart, notification count, selected filters |
For parent→child one level: use `[Parameter]` / `EventCallback` (see `author-component` skill). For persisting state across prerender→interactive: see `support-prerendering` skill.
Workflow (quick reference)
1. Choose the mechanism from the table in Step 2 2. If crossing render mode boundaries → use `CascadingValueSource<T>` (Step 4) 3. Register in `Program.cs` with `AddCascadingValue(...)` and `isFixed: false` 4. Consume via `[CascadingParameter]` in child components 5. Update via `NotifyChangedAsync(newValue)` — never page reload 6. For additional mutable state within a circuit → add scoped service (Step 5) 7. Wrap any `StateHasChanged` from background threads in `InvokeAsync` 8. Implement `IDisposable` — dispose timers, cancel tokens, unsubscribe events
Step 3 — CascadingValue for subtree state
Wrap a subtree with `<CascadingValue>` to flow data to all descendants without passing it through every intermediate component.
@* In a layout or parent component *@
<CascadingValue Value="theme">
@Body
</CascadingValue>
@code {
private ThemeInfo theme = new() { ButtonClass = "btn-primary" };
}Consume in any descendant:
[CascadingParameter]
private ThemeInfo? Theme { get; set; }**Rules:**
- Matched by **type**, not name. To cascade multiple values of the same type, add `Name`:
<CascadingValue Value="primary" Name="PrimaryTheme">...</CascadingValue>
[CascadingParameter(Name = "PrimaryTheme")]
private ThemeInfo? Primary { get; set; }- Set `IsFixed="true"` when the value never changes — avoids subscription overhead.
- **Does NOT cross render mode boundaries.** A `<CascadingValue>` in a static SSR parent is invisible to interactive children. See Step 6.
Step 4 — CascadingValueSource<T> for app-wide state
Register a `CascadingValueSource<T>` in DI when the value must be available to **all components regardless of render mode**.
// Program.cs
builder.Services.AddCascadingValue(sp =>
{
var theme = new ThemeInfo { ButtonClass = "btn-primary" };
return new CascadingValueSource<ThemeInfo>(theme, isFixed: false);
});Consume identically to Step 3:
[CascadingParameter]
private ThemeInfo? Theme { get; set; }**To update and notify subscribers**, either mutate the existing object or replace it:
@* Component that changes the theme *@
@inject CascadingValueSource<ThemeInfo> ThemeSource
<button @onclick="ToggleDarkMode">Toggle theme</button>
@code {
private bool isDark;
private async Task ToggleDarkMode()
{
isDark = !isDark;
// Replace the value entirely:
var newTheme = new ThemeInfo { ButtonClass = isDark ? "btn-dark" : "btn-primary" };
await ThemeSource.NotifyChangedAsync(newTheme);
}
}`NotifyChangedAsync()` (no argument) also works — mutate the object and then call it. `NotifyChangedAsync(newValue)` replaces the value and notifies in one step.
**Update protocol:** Whenever shared state changes, the component that changes it MUST inject `CascadingValueSource<T>` and call `NotifyChangedAsync()`. This is the only mechanism that triggers re-rendering in all `[CascadingParameter]` subscribers. Without this call, no subscribers update. Do not use `NavigationManager.Refresh()` or page reloads as a substitute.
**Rules:**
- `isFixed: false` enables change notifications. `isFixed: true` is better for truly static values (feature flags).
- **Crosses render mode boundaries** — works for per-page interactivity, global interactivity, and WebAssembly. Key advantage over `<CascadingValue>`.
- Keep cascaded types **granular**. Every `NotifyChangedAsync` re-renders ALL subscribers regardless of which property changed. Don't put all app state into one cascaded type.
- For Auto/WebAssembly apps, register in **both** server and `.Client` `Program.cs`. The type must be in a shared assembly.
Step 5 — Scoped state service with change events
For mutable shared state that multiple components read **and write** (shopping cart, notification count, filters), use a scoped service with an event for change notification.
**Define the service:**
public class CartState
{
private readonly List<CartItem> _items = [];
public IReadOnlyList<CartItem> Items => _items;
public intRead more
license: MIT name: coordinate-components description: > Share state between components that don't have a direct parent-child parameter relationship, using cascading values, scoped services with change events, or CascadingValueSource via DI. USE WHEN the user needs a CascadingParameter or CascadingValue that works across render mode boundaries, a shopping cart or notification count accessible from multiple pages, a theme or user preference cascaded app-wide, or when components in different parts of the tree must react when shared data changes. Also USE WHEN cascading values aren't reaching interactive children in per-page interactivity mode, or when the user needs to understand scoped vs singleton service lifetime for state on Blazor Server. DO NOT USE for direct parent-child parameter passing or EventCallback (see author-component), for persisting state across prerender-to-interactive transitions (see support-prerendering), or for service abstractions for data fetching in Auto/WebAssembly (see fetch-and-send-data).
Coordinate Components
Step 1 — Read AGENTS.md
Read `AGENTS.md` at the workspace root to learn the project's conventions before making changes.
Step 2 — Decide the scope
| Need | Mechanism | When to use | |------|-----------|-------------| | Subtree (same render mode) | `CascadingValue` component | Theme, layout config within a layout | | App-wide (all render modes) | `CascadingValueSource<T>` via DI | Current user, feature flags, theme shared globally | | Mutable shared state within a circuit | Scoped service + `Action` event | Shopping cart, notification count, selected filters |
For parent→child one level: use `[Parameter]` / `EventCallback` (see `author-component` skill). For persisting state across prerender→interactive: see `support-prerendering` skill.
Workflow (quick reference)
1. Choose the mechanism from the table in Step 2 2. If crossing render mode boundaries → use `CascadingValueSource<T>` (Step 4) 3. Register in `Program.cs` with `AddCascadingValue(...)` and `isFixed: false` 4. Consume via `[CascadingParameter]` in child components 5. Update via `NotifyChangedAsync(newValue)` — never page reload 6. For additional mutable state within a circuit → add scoped service (Step 5) 7. Wrap any `StateHasChanged` from background threads in `InvokeAsync` 8. Implement `IDisposable` — dispose timers, cancel tokens, unsubscribe events
Step 3 — CascadingValue for subtree state
Wrap a subtree with `<CascadingValue>` to flow data to all descendants without passing it through every intermediate component.
@* In a layout or parent component *@
<CascadingValue Value="theme">
@Body
</CascadingValue>
@code {
private ThemeInfo theme = new() { ButtonClass = "btn-primary" };
}Consume in any descendant:
[CascadingParameter]
private ThemeInfo? Theme { get; set; }**Rules:**
- Matched by **type**, not name. To cascade multiple values of the same type, add `Name`:
<CascadingValue Value="primary" Name="PrimaryTheme">...</CascadingValue>
[CascadingParameter(Name = "PrimaryTheme")]
private ThemeInfo? Primary { get; set; }- Set `IsFixed="true"` when the value never changes — avoids subscription overhead.
- **Does NOT cross render mode boundaries.** A `<CascadingValue>` in a static SSR parent is invisible to interactive children. See Step 6.
Step 4 — CascadingValueSource<T> for app-wide state
Register a `CascadingValueSource<T>` in DI when the value must be available to **all components regardless of render mode**.
// Program.cs
builder.Services.AddCascadingValue(sp =>
{
var theme = new ThemeInfo { ButtonClass = "btn-primary" };
return new CascadingValueSource<ThemeInfo>(theme, isFixed: false);
});Consume identically to Step 3:
[CascadingParameter]
private ThemeInfo? Theme { get; set; }**To update and notify subscribers**, either mutate the existing object or replace it:
@* Component that changes the theme *@
@inject CascadingValueSource<ThemeInfo> ThemeSource
<button @onclick="ToggleDarkMode">Toggle theme</button>
@code {
private bool isDark;
private async Task ToggleDarkMode()
{
isDark = !isDark;
// Replace the value entirely:
var newTheme = new ThemeInfo { ButtonClass = isDark ? "btn-dark" : "btn-primary" };
await ThemeSource.NotifyChangedAsync(newTheme);
}
}`NotifyChangedAsync()` (no argument) also works — mutate the object and then call it. `NotifyChangedAsync(newValue)` replaces the value and notifies in one step.
**Update protocol:** Whenever shared state changes, the component that changes it MUST inject `CascadingValueSource<T>` and call `NotifyChangedAsync()`. This is the only mechanism that triggers re-rendering in all `[CascadingParameter]` subscribers. Without this call, no subscribers update. Do not use `NavigationManager.Refresh()` or page reloads as a substitute.
**Rules:**
- `isFixed: false` enables change notifications. `isFixed: true` is better for truly static values (feature flags).
- **Crosses render mode boundaries** — works for per-page interactivity, global interactivity, and WebAssembly. Key advantage over `<CascadingValue>`.
- Keep cascaded types **granular**. Every `NotifyChangedAsync` re-renders ALL subscribers regardless of which property changed. Don't put all app state into one cascaded type.
- For Auto/WebAssembly apps, register in **both** server and `.Client` `Program.cs`. The type must be in a shared assembly.
Step 5 — Scoped state service with change events
For mutable shared state that multiple components read **and write** (shopping cart, notification count, filters), use a scoped service with an event for change notification.
**Define the service:**
public class CartState
{
private readonly List<CartItem> _items = [];
public IReadOnlyList<CartItem> Items => _items;
public intThis repository contains the .NET team's curated set of portable skills and host-specific custom agents for coding agents. For information about the Agent Skills standard, see agentskills.io.
Other skills on skills.
More skills from other plugins.
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
ask-matt
Ask which skill or flow fits your situation. A router over the skills in this repo.
Questions about coordinate-components.
- Which coding agents can use coordinate-components?
- coordinate-components installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.

