Course 08 · Blazor · Components

Build a Button Component

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.

Follow along in Visual Studio. Each step shows exactly which file to create or open and what to type — use the Copy button if you get stuck. Tip: click the button to go full screen and show just the current step. The last two steps run live, right here in the page.
Step 1 of 14

Today's goal

Build a Button Component

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:

  • Create a Blazor component with its own markup and scoped CSS.
  • Make an element look and feel clickable, and react to hover, press and keyboard focus.
  • Handle clicks and key presses in C# with @onclick and @onkeydown.
  • Expose [Parameter]s so other pages can reuse your button.

Homework review

How did the homework go?

Quick show of hands — who managed each of these from last time? Then ask about anything that tripped you up.

  • Who finished the calculator?
  • Who built the “are you on holidays?” if/else page?
  • (Advanced) Who attempted the SVG diagram / organism-classification challenge?
Questions? Now's the time — what was confusing or surprising?

Recap

Fast-fire: what do we remember?

Shout out the answers — quick questions on things we've already covered.

  • What is Blazor?
  • What's the difference between a page and a component?
  • What does an event handler do? Name an event we've used.
  • One-way vs two-way binding — what's the difference?
  • What does an if / else statement let our code do?

Fast-fire · together

Spot the error

Look at this Blazor snippet and shout out what's wrong — there's more than one bug.

Spot the error
Counter.razor
<p>Count: @count</p>
<button onclick="Increment">Add one</button>

@code {
    int count = 0;

    void Increment()
    {
        count++
    }
}
Answers: the click handler must be Blazor's directive @onclick="Increment" (not HTML's onclick), and count++ is missing its semicolon — count++;.

Build · The blank canvas

Start with a div

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.

Do this: In Visual Studio, right-click the project → Add → Razor Component, name it MyButton.razor, and replace its contents with the code below.
MyButton.razor
<div>Click me</div>
Notice: Nothing about this says "button". The cursor stays an arrow, and a user would never guess they can interact with it. We'll fix every bit of that ourselves.

Build · Make it look like a button

Give it shape with scoped CSS

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:

MyButton.razor
<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.

Do this: create a new file called 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.
MyButton.razor.css — create this file
.my-button {
    display: inline-block;
    background: #D81B72;
    color: #fff;
    padding: 12px 28px;
    border-radius: 8px;
}
Still off: hover over it and the mouse shows a text cursor, not a hand. It looks like a button but doesn't feel like one yet.

Build · Make it feel clickable

Cursor and selection

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.

MyButton.razor.css — add to .my-button
    cursor: pointer;      /* hand cursor */
    user-select: none;    /* don't highlight the label */

Build · React to the mouse

The hover state

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.

MyButton.razor.css
.my-button {
    /* ...previous styles... */
    transition: background 0.15s;
}

.my-button:hover {
    background: #A8155A;
}

Build · React to the press

The active state

: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.

MyButton.razor.css
.my-button {
    /* ...previous styles... */
    transition: background 0.15s, transform 0.1s;
}

.my-button:active {
    background: #870f48;
    transform: scale(0.97);
}
Think: a person using only a keyboard can't hover or press with a mouse. How would they use this button at all? That's the next step.

Build · Reach everyone

Keyboard focus

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.

MyButton.razor
<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:

MyButton.razor.css
.my-button {
    /* ...previous styles... */
    outline: none;
}

.my-button:focus-visible {
    outline: 3px solid #6D28D9;
    outline-offset: 3px;
}
Rule: never remove the outline without giving one back. Hiding focus entirely locks keyboard users out — a real accessibility failure.

Build · Make it do something

Handle clicks and keys in C#

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#:

MyButton.razor
<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

Parameters: the reusable button

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.

MyButton.razor — finished
<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:

Home.razor — using the component
<MyButton OnClick="Save">Save changes</MyButton>

@code {
    private void Save()
    {
        Console.WriteLine("Saved!");
    }
}
▶ Live — the finished MyButton (try the mouse, then Tab + Enter/Space)
✎ Exercise — reach the goal, with mouse or keyboard
Why build all this? A native <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

Exercises — pick your level

Use your new MyButton component. Start where you're comfortable and climb as far as you can.

  • Easy

    Change the look

    Edit MyButton.razor.css to give your button a new colour, rounder corners, or a bigger size.

  • Easy

    Use it twice

    Put two <MyButton>s on a page with different labels, each showing a different message when clicked.

  • Medium

    Click counter

    Make a page where a MyButton increases a number on screen each time it's clicked.

  • Medium

    Disabled state

    Add a Disabled parameter to MyButton that greys it out and ignores clicks.

  • Hard

    Icon + label

    Let the caller show an emoji or icon before the text (via ChildContent or a new parameter).

  • Hard

    Toggle button

    Make a MyButton that switches between two states (e.g. “Follow” / “Following”) each press.

Homework

Before next time

Finish these at home — we'll review them at the start of the next lesson.

  • Core

    Click counter

    Complete the click-counter exercise above using your MyButton.

  • Core

    Disabled state

    Add and test the Disabled parameter.

  • Stretch

    Toggle button

    Build the “Follow / Following” toggle button.