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 .razor file compiles to a C# class extending ComponentBase — 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.