Course 08 · Blazor · Components
We build a real, reusable Blazor component — MyButton — starting from a plain <div>. One layer at a time, you'll see exactly what a native button gives you for free, and how to recreate it yourself in C# and CSS.
Today's goal
Build your own reusable Blazor MyButton component from a plain <div> — and understand everything a real button does for you under the hood.
By the end you'll be able to:
@onclick and @onkeydown.[Parameter]s so other pages can reuse your button.Homework review
Quick show of hands — who managed each of these from last time? Then ask about anything that tripped you up.
Recap
Shout out the answers — quick questions on things we've already covered.
if / else statement let our code do?Fast-fire · together
Look at this Blazor snippet and shout out what's wrong — there's more than one bug.
<p>Count: @count</p>
<button onclick="Increment">Add one</button>
@code {
int count = 0;
void Increment()
{
count++
}
}
@onclick="Increment" (not HTML's onclick), and count++ is missing its semicolon — count++;.Build · The blank canvas
A Blazor component is just a .razor file. The most basic thing we can put in it is a <div> — a plain box with no meaning, no style, and no behaviour. This is our starting point.
MyButton.razor, and replace its contents with the code below.<div>Click me</div>
Build · Make it look like a button
Blazor lets each component carry its own styles in a matching MyButton.razor.css file — these are scoped, so they only affect this component. First, give the div a class:
<div class="my-button">Click me</div>
Then style it. padding adds inner space, background gives colour, border-radius rounds the corners, and display: inline-block makes padding behave.
MyButton.razor.css in the same folder as MyButton.razor (right-click the project → Add → New Item → Style Sheet). Blazor links it to the component automatically by matching the name..my-button {
display: inline-block;
background: #D81B72;
color: #fff;
padding: 12px 28px;
border-radius: 8px;
}
Build · Make it feel clickable
Two small lines fix the feel. cursor: pointer shows the hand cursor that says "click me", and user-select: none stops the label from being highlighted when you click. Native buttons do both automatically — we add them by hand.
cursor: pointer; /* hand cursor */
user-select: none; /* don't highlight the label */
Build · React to the mouse
A pseudo-class is a selector that activates when something happens. :hover fires while the mouse is over the element. We darken the colour, and add transition so the change fades smoothly instead of snapping.
.my-button {
/* ...previous styles... */
transition: background 0.15s;
}
.my-button:hover {
background: #A8155A;
}
Build · React to the press
:active fires while the mouse button is held down. We darken it further and shrink it slightly with transform: scale(0.97). That tiny "push" makes the button feel physical and responsive.
.my-button {
/* ...previous styles... */
transition: background 0.15s, transform 0.1s;
}
.my-button:active {
background: #870f48;
transform: scale(0.97);
}
Build · Reach everyone
Many people navigate with the keyboard alone. tabindex="0" puts our div into the normal tab order, and role="button" tells screen readers to announce it as a button rather than a plain box.
<div class="my-button" role="button" tabindex="0">
Click me
</div>
Then show a clear focus ring — but only for keyboard users — with :focus-visible:
.my-button {
/* ...previous styles... */
outline: none;
}
.my-button:focus-visible {
outline: 3px solid #6D28D9;
outline-offset: 3px;
}
Build · Make it do something
Now the Blazor part. We wire @onclick for mouse and touch, and @onkeydown for the keyboard. A native button activates on Enter and Space — our div doesn't know to, so we tell it, in C#:
<div class="my-button" role="button" tabindex="0"
@onclick="HandleClick"
@onkeydown="HandleKeyDown">
Click me
</div>
@code {
private void HandleClick()
{
// We'll do something useful here next.
}
private void HandleKeyDown(KeyboardEventArgs e)
{
if (e.Key == "Enter" || e.Key == " ")
{
HandleClick();
}
}
}
Build · Turn it into a real component
A component is only useful if other parts of the app can reuse it. We add two parameters: ChildContent lets the caller choose the label, and OnClick is an EventCallback the parent handles — exactly like the built-in button.
<div class="my-button" role="button" tabindex="0"
@onclick="HandleClick"
@onkeydown="HandleKeyDown">
@ChildContent
</div>
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public EventCallback OnClick { get; set; }
private async Task HandleClick()
{
await OnClick.InvokeAsync();
}
private async Task HandleKeyDown(KeyboardEventArgs e)
{
if (e.Key == "Enter" || e.Key == " ")
{
await OnClick.InvokeAsync();
}
}
}
Now any page can use it like this — the caller decides the label and what happens on click:
<MyButton OnClick="Save">Save changes</MyButton>
@code {
private void Save()
{
Console.WriteLine("Saved!");
}
}
<button> gives you all of this for free. Recreating it teaches you what the browser actually does — and what every Blazor component you ever write is made of: markup, scoped style, parameters, and event callbacks.
Your turn
Use your new MyButton component. Start where you're comfortable and climb as far as you can.
Edit MyButton.razor.css to give your button a new colour, rounder corners, or a bigger size.
Put two <MyButton>s on a page with different labels, each showing a different message when clicked.
Make a page where a MyButton increases a number on screen each time it's clicked.
Add a Disabled parameter to MyButton that greys it out and ignores clicks.
Let the caller show an emoji or icon before the text (via ChildContent or a new parameter).
Make a MyButton that switches between two states (e.g. “Follow” / “Following”) each press.
Homework
Finish these at home — we'll review them at the start of the next lesson.
Complete the click-counter exercise above using your MyButton.
Add and test the Disabled parameter.
Build the “Follow / Following” toggle button.