Support compile-time propagation of Web Component slot attributes through RenderFragment roots
- Dominant language
- C#
- Stars
- 20.7k
- Forks
- 4.3k
- PR merge metrics
- PR metrics pending
Description
## Background
ASP.NET Core issue [dotnet/aspnetcore#68954](https://github.com/dotnet/aspnetcore/issues/68954) describes a gap when a Razor component wraps a Web Component with named slots.
A wrapper commonly exposes named content as a `RenderFragment`:
```csharp
[Parameter]
public RenderFragment? Icon { get; set; }
```
and consumers provide ordinary Razor content:
```razor
settings
Save
```
The Web Component requires the rendered light-DOM root to carry the native `slot` attribute:
```html
settings
Save
```
Wrapping the fragment in an element does not preserve these semantics because the wrapper becomes the assigned slotted node. A `RenderFragment` is opaque at runtime, so the wrapper cannot add the attribute to its roots using supported APIs.
## Proposal
Add declarative metadata to a `RenderFragment` component parameter so the Razor compiler can propagate a Web Component slot assignment while generating the fragment.
The API names below are illustrative:
```csharp
namespace Microsoft.AspNetCore.Components;
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class SlotAttribute : Attribute
{
public SlotAttribute(string name);
public string Name { get; }
}
public interface ISlotReceiver
{
string? Slot { get; set; }
}
```
A wrapper would declare:
```csharp
[Parameter]
[Slot("icon")]
public RenderFragment? Icon { get; set; }
```
For inline Razor child content, the compiler would apply the following rules to each statically known top-level root:
1. For an HTML element, emit `slot="icon"` directly on the element.
2. For a component, pass the slot value to the component.
3. For roots inside compiler-visible control flow such as `@if` and `@foreach`, apply the same rules in each branch or iteration.
4. For multiple roots, apply the slot to every root.
5. Ignore formatting whitespace and emit nothing for an empty fragment.
Conceptually, this:
```razor
settings
alternate
```
would generate fragment code equivalent to:
```csharp
builder.OpenElement(0, "span");
builder.AddAttribute(1, "slot", "icon");
builder.AddContent(2, "settings");
builder.CloseElement();
builder.OpenComponent(3);
builder.AddAttribute(4, "slot", "icon");
builder.AddContent(5, "alternate");
builder.CloseComponent();
```
## Component forwarding
A Blazor component has no corresponding DOM node, so a slot assignment passed to a component must be forwarded until it reaches the component's top-level HTML roots.
For Razor-generated components deriving from `ComponentBase`, implementing `ISlotReceiver` would opt into compiler-generated behavior. The Razor compiler would provide the slot parameter implementation and propagate its value to that component's statically known top-level roots using the same rules above.
This makes propagation local to each compilation unit:
- A parent fragment passes the slot to an immediate component root.
- The receiving Razor component passes it to its immediate roots.
- Propagation continues through participating components until an HTML element receives the native attribute.
Components for which Razor cannot generate the implementation, including custom `IComponent` implementations, would implement the interface and forwarding behavior themselves. An explicit `Slot` parameter or unmatched-attribute handling could also provide a manual implementation path.
## Unsupported and diagnostic cases
The feature should not inspect or rewrite a rendered tree at runtime. Consequently, the compiler should report a diagnostic when it cannot prove how to propagate the slot, including cases such as:
```razor
@existingFragment
```
or:
```razor
```
Other cases requiring defined diagnostics include:
- A non-whitespace text or markup root, because it cannot carry a named `slot` attribute.
- A component root that cannot receive or forward the slot value.
- A root that already specifies a conflicting `slot` value.
## Non-goals
- Traversing the component tree or render tree at runtime.
- Invoking a fragment into a scratch `RenderTreeBuilder` and replaying or mutating its frames.
- Exposing `RenderTreeFrame` as a supported application-level mutation API.
- Supporting arbitrary opaque `RenderFragment` values whose roots are not visible to the compiler.
The intent is an 80/20 compiler feature for inline Razor markup, with explicit opt-in propagation through Razor components and clear diagnostics when compile-time propagation is not possible.
Contributor guide
Research direction
Start by reviewing the Razor compiler entry points involved in generating RenderFragment content and component parameters. Define how SlotAttribute and ISlotReceiver should participate in propagation, including control flow, forwarding, and unsupported roots. Done means inline roots receive the slot value at compile time and clear diagnostics cover opaque fragments, conflicts, and non-forwarding components.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100