Blazor tutorial
Razor class library components with parameters, ChildContent render fragments, attribute splatting, and a bUnit test per component.
What you'll build
A small contact form — labelled input, submit button, @bind-Value binding, real keyboard and screen-reader
behaviour — first bare, then styled, in about 10 minutes.
Before you start
You need git and the .NET SDK 8 or later. Any Blazor project works — Blazor Web App, Server, or WebAssembly.
Step 1 — Get the code
git clone https://github.com/LilyDesignSystem/lily-design-system
cd lily-design-system/lily-design-system-blazor-headless
dotnet build
dotnet test # bUnit — 1,400+ cases across the catalog Components live in src/LilyBlazorHeadless/Components/ — Button.razor, TextInput.razor — one Razor file
each. Reference the project (or copy the files) from your Blazor app and
add @using LilyBlazorHeadless.Components to _Imports.razor.
Step 2 — Your first component
<Button OnClick="Save">Save</Button> Renders <button class="button">. The shared parameter
conventions: Label (an aria-label override), CssClass (appended to the base hook), ChildContent, and [Parameter(CaptureUnmatchedValues = true)] attribute
splatting so any extra HTML attribute passes through.
Step 3 — Compose a small form
<Form OnSubmit="HandleSubmit">
<Field Label="Full name" Required="true">
<TextInput Id="name" Label="Full name" @bind-Value="name" Required="true" />
</Field>
<Button Type="submit">Save</Button>
</Form>
@code {
private string name = "";
} Field takes the visible Label as its own
parameter rather than a nested label component — pass it once, on Field, and TextInput's matching Label supplies the accessible name.
Verified 2026-08-29: an earlier draft nested a
separate Label component inside Field, which
renders two labels (one empty) because Field already renders its own from the Label parameter —
caught by checking the component source; Form itself has
no Label parameter, so the earlier draft's <Form> usage was corrected to omit one rather than
invent an attribute that does not exist. The contact-form composed route is the same pattern, exercised by real e2e and axe tests.
Check your work: run the app and the form looks plain — exactly as intended: styling is still ahead. Press Tab: focus moves through every control, the label is announced, and submit works. The behaviour is done; the look is yours, and it's next.
Step 4 — Style it
Style the class hooks — .button, .text-input, .label, .field — in app.css, or
link a ready-made theme in App.razor / index.html:
<link rel="stylesheet" href="themes/wireframe.css" /> Step 5 — See it all working
The Blazor Web example app shows the full catalog styled and demoed, with Playwright e2e tests:
git clone https://github.com/LilyDesignSystem/lily-design-system
cd lily-design-system/lily-design-system-blazor-web-examples
dotnet run --project src/LilyBlazorWebExamples Next steps
- Theming — runtime switching with the Blazor theme-picker helper (a Razor class library on NuGet).
- Preference helpers — language and text size.
- Component catalog — props, ARIA, and keyboard per component.
Questions along the way? The help page is full of answers — and if a step could be clearer, tell us and we'll gladly improve the tutorial.