Components & Razor
Phase 1 showed the big picture: a Blazor app is a tree of components, and you choose where the C# runs. Now we open up a single component and look inside - where Blazor stops being an abstract idea and becomes something you can actually type.
Here's the mental model to hold before touching any code: a component is markup plus a @code block, compiled together into one C# class. The HTML you write is the shape of the UI; the @code block is the data and behavior behind it; the @ symbol is the bridge that lets one reach into the other. Once that clicks, every piece of Razor you see is a variation on "HTML, with C# woven in through @."
📝 A component lives in a
.razorfile, and the file name is the component name.ProductCard.razordefines a component you'll later use as<ProductCard />. PascalCase, always - the compiler turns the file name into a class name, and C# classes are PascalCase.
The two halves of a component
Let's build the running example for this guide: a ProductCard that shows a single product. Start with the smallest version that has both halves - some markup, and a @code block holding the data it renders.
<div class="card">
<h3>@product.Name</h3>
<p>@product.Price.ToString("C")</p>
</div>
@code {
private Product product = new() { Name = "Mechanical Keyboard", Price = 89.99m };
private record Product
{
public string Name { get; init; } = "";
public decimal Price { get; init; }
}
}
What just happened: the top part is plain HTML except for @product.Name and @product.Price... - Razor expressions: @ followed by C# that gets evaluated and dropped into the page. The @code { } block is ordinary C#: a field (product) and a little record type. When this component renders, Blazor runs the C#, evaluates each @, and produces the final HTML. Markup on top, code on the bottom, @ stitching them together.
Razor essentials
Everything in Razor flows from one symbol. @ means "switch from HTML into C# here." How much C# follows depends on what comes after the @.
@expression renders a value. This is the most common case - @ followed by a simple expression, and Razor writes the result into the HTML:
<p>@product.Name</p>
<p>You have @items.Count items.</p>
What just happened: @product.Name evaluates the property and renders the string; @items.Count does the same with an int - Razor calls .ToString() for you. The @ reaches into C#, grabs the value, and the rest of the line stays HTML.
When the expression has spaces or operators that Razor might confuse with surrounding markup, wrap it in @(...) to be explicit:
<p>Total: @(product.Price * quantity)</p>
What just happened: the parentheses tell Razor exactly where the C# starts and stops - without them, it might try to read the * or the space as markup. When in doubt, @(...).
Control flow uses C# directly in the markup. There's no special template language for loops and conditionals - you write real @if, @foreach, @for, @switch, and the markup inside the braces gets rendered each time through:
@if (product.InStock)
{
<span class="badge">In stock</span>
}
else
{
<span class="badge muted">Sold out</span>
}
What just happened: @if is a normal C# if, but the braces hold markup instead of statements - C# control flow that emits HTML.
Loops work the same way - here's the product list that will sit alongside ProductCard:
<ul class="product-list">
@foreach (var p in products)
{
<li>@p.Name - @p.Price.ToString("C")</li>
}
</ul>
@code {
private List<Product> products = new()
{
new() { Name = "Mechanical Keyboard", Price = 89.99m },
new() { Name = "USB-C Hub", Price = 34.50m },
new() { Name = "Laptop Stand", Price = 42.00m },
};
}
What just happened: @foreach loops over products, and the <li> inside the braces renders once per item - three products means three <li> elements. Exactly how you'd loop in C#, just emitting markup instead of writing to a console.
💡 One thing that trips people up: how do you print a literal
@sign? An email address like[email protected]in your markup would make Razor think@exampleis C#. Escape it by doubling:you@@example.comrenders a single@.
Directives: pages vs reusable components
At the very top of a .razor file you'll often see lines starting with @ that aren't expressions - they're directives. They configure the component itself rather than rendering anything.
The one you'll meet first is @page:
@page "/products"
<h1>Our Products</h1>
<ul class="product-list">
@foreach (var p in products)
{
<li>@p.Name - @p.Price.ToString("C")</li>
}
</ul>
@code {
private List<Product> products = new() { /* ... */ };
}
What just happened: @page "/products" makes this component a routable page - navigate to /products and Blazor renders it. Without @page, a component isn't reachable by URL on its own.
📝 The key distinction: a component with
@pageis a page - it has a URL the router can land on. A component without@pageis a reusable piece -ProductCardhas no URL; you drop its tag inside another component (<ProductCard />). Pages are destinations; reusable components are the building blocks assembled inside them. We'll wire data into<ProductCard Product="p" />in Phase 6.
Two other directives you'll see soon (don't worry about the details yet):
@using MyApp.Models
@inject ProductService Products
What just happened: @using imports a namespace, exactly like a using at the top of a C# file - write Product instead of MyApp.Models.Product. @inject asks for a service via dependency injection, how a component gets things like an HttpClient to load data. Covered properly in Phase 7: Calling APIs & Dependency Injection - for now, recognize the shape.
Composing components into a tree
A component becomes useful when other components use it. You render one by writing its name as an HTML tag. Here's a products page that uses ProductCard:
@page "/products"
<h1>Our Products</h1>
<div class="grid">
<ProductCard />
<ProductCard />
<ProductCard />
</div>
What just happened: each <ProductCard /> tells Blazor to render the ProductCard component right there. The page is a component; it contains three ProductCard components; each is its own markup-plus-@code unit. That nesting is the component tree from Phase 1 - a page at the top, smaller components inside it, all the way down. (These three cards show the same hardcoded product for now; passing each its own data is what Phase 6's Product="p" parameter does.)
A note on what "compiled" really means
Something that saves real debugging time once internalized: Razor is compiled, not interpreted. Your markup and @code block aren't read line-by-line at runtime - they're compiled together into a regular C# class before the app ever runs.
⚠️ The practical consequence: mistakes in Razor are compile errors, not runtime errors. A mismatched
{ }, an unclosed tag in a@foreach, or a stray@where you meant a literal - these stop the build with an error message, same as a typo in any C# file. The compiler catches a whole category of bugs before the page ever loads. When the build fails after editing a.razorfile, read the error like any C# compile error - it's pointing at your markup.
Recap
- A component is markup + a
@codeblock, compiled into one C# class;@is the bridge from HTML into C#. @expressionrenders a value; use@(...)when the expression isn't a simple member access; double@@to print a literal@.- Control flow is real C# in the markup -
@if/@else,@foreach,@for,@switch- with HTML inside the braces. - A component with
@pageis a routable page (it has a URL); a component without@pageis a reusable piece you place as a tag, like<ProductCard />. - You compose components by using them as tags; nested tags form the component tree.
- Razor compiles - brace, tag, and
@mistakes are compile errors, caught at build time, not runtime.
Quick check
Test the mental model before moving on:
[
{
"q": "What does a component's @code block hold?",
"choices": ["The CSS styles for the component", "The component's fields and methods (its C# logic)", "A list of other components to import", "The compiled HTML output"],
"answer": 1,
"explain": "The @code block is ordinary C# - fields, methods, and types - that compiles together with the markup into a single class."
},
{
"q": "What is the difference between a component with @page and one without it?",
"choices": ["@page makes it run on the server instead of WebAssembly", "@page makes it a routable page with a URL; without it, the component is used as a tag inside other components", "@page is required for every .razor file", "Without @page the component cannot have a @code block"],
"answer": 1,
"explain": "@page \"/products\" gives a component a URL the router can land on. Reusable components like ProductCard have no @page - you place them as tags such as <ProductCard />."
},
{
"q": "You have a mismatched brace inside a @foreach loop in your .razor file. When does it surface?",
"choices": ["At runtime, when the loop first executes", "As a compile error at build time", "Only when a user visits the page", "It is silently ignored and the loop is skipped"],
"answer": 1,
"explain": "Razor is compiled, not interpreted. Markup and @code compile into a C# class, so brace and @ mistakes are compile errors caught at build time."
}
]
Before the quiz: without looking back, say (or jot down) the core idea of this phase in your own words.
Check your understanding 3 questions
1. What does a component's @code block hold?
2. What is the difference between a component with @page and one without it?
3. You have a mismatched brace inside a @foreach loop in your .razor file. When does it surface?