In Blazor, a component is not a template with a code-behind file — it is a C# class. Every .razor file compiles into a class that inherits from ComponentBase. The markup section compiles into a method called BuildRenderTree. This matters because everything you know about C# classes applies: you can add properties, methods, and inheritance to control exactly what a component does and how it receives data.
What a Component Actually Is
When the compiler processes ProductCard.razor, it produces something equivalent to this:
public partial class ProductCard : ComponentBase
{
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
// Generated from the Razor markup
builder.OpenElement(0, "div");
builder.AddAttribute(1, "class", "product-card");
// ...
builder.CloseElement();
}
}
The @code { } block is the rest of that same partial class. There is no magic — it is just C#.
Why Extract Components
Inline markup that repeats is a sign that a component is ready to be extracted. Compare the same list before and after:
@* BEFORE: everything inline, hard to read and impossible to reuse *@
@foreach (var product in products)
{
<div class="product-card">
<h3>@product.Name</h3>
<p>$@product.Price.ToString("F2")</p>
<span class="badge @(product.InStock ? "badge-green" : "badge-red")">
@(product.InStock ? "In Stock" : "Out of Stock")
</span>
</div>
}
@* AFTER: template is clean; all logic lives in the child component *@
@foreach (var product in products)
{
<ProductCard Product="product" />
}
[Parameter] — Receiving Data from a Parent
A public property decorated with [Parameter] becomes an attribute on the component's HTML-like tag. Blazor sets it before the first render.
// ProductCard.razor
@code {
[Parameter] public Product Product { get; set; } = default!;
}
@* Parent usage *@
<ProductCard Product="@myProduct" />
One rule you must never break: do not modify a [Parameter] property inside the child component. Parameters are owned by the parent; if the child changes them, the next re-render from the parent will overwrite the change and you will spend an hour wondering why your edit disappears.
[EditorRequired] — Compile-Time Safety
Add [EditorRequired] alongside [Parameter] and the compiler (and IDE) will warn when a parent uses the component without supplying that attribute:
[Parameter, EditorRequired] public Product Product { get; set; } = default!;
This catches the "I forgot to pass the required prop" mistake at compile time rather than at runtime.
Optional Parameters with Defaults
Assign a default value to make a parameter optional. The parent can omit it; the child falls back to the default.
[Parameter] public string CurrencySymbol { get; set; } = "$";
[Parameter] public bool ShowStock { get; set; } = true;
Computed Properties Inside Components
Presentation logic — deciding a CSS class, formatting a value — belongs in a computed property, not inline in the markup:
// ProductCard.razor @code block
private string BadgeBg => Product.InStock ? "bg-green-100" : "bg-red-100";
private string BadgeText => Product.InStock ? "text-green-700" : "text-red-700";
private string BadgeLabel => Product.InStock ? "In Stock" : "Out of Stock";
<span class="badge @BadgeBg @BadgeText">@BadgeLabel</span>
The markup becomes declarative. If the business rule changes (maybe a "Low Stock" state is added), you update the C# property — not a ternary buried in HTML.
One-Way Data Flow
The correct mental model is: the parent owns all state. The child displays it. Data flows down through parameters, never up through mutation. When the child needs to signal a change back to the parent, it does so through events — specifically EventCallback<T>, covered in the next article.
@* Parent owns selectedProductId *@
<ProductList Products="products" SelectedId="selectedProductId" />
<ProductDetail ProductId="selectedProductId" />
@* ProductList raises an event; the parent updates selectedProductId *@
@code {
private int selectedProductId = 0;
}
Key Takeaways
- A
.razorfile compiles to a C# class extendingComponentBase— every C# class feature applies. - Extract a child component whenever a block of markup repeats or grows too complex to read.
[Parameter]exposes a public property as an attribute the parent can set.[EditorRequired]produces a compile-time warning when the attribute is omitted — use it for mandatory parameters.- Never mutate a
[Parameter]property inside the child; it is owned by the parent. - Computed properties keep presentation logic out of the template and make it unit-testable.