Blazor tutorial

Razor class library components wi parameters, ChildContent render fragments, attribute splattin, an a bUnit test per component.

Whit ye'll big

A wee contact form — labelled input, submit button, @bind-Value bindin, real keyboard an screen-reader behaviour — first bare, then styled, in aboot 10 minutes.

Afore ye stairt

Ye need git an the .NET SDK 8 or later. Onie Blazor project wirks — Blazor Web App, Server, or WebAssembly.

Step 1 — Git 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

The components live in src/LilyBlazorHeadless/Components/ — Button.razor, TextInput.razor — ae Razor file ilkane. Reference the project (or copy the files) frae yer Blazor app an add @using LilyBlazorHeadless.Components tae _Imports.razor.

Step 2 — Yer first component

<Button OnClick="Save">Save</Button>

Renders <button class="button">. The shared parameter conventions: Label (an aria-label owerride), CssClass (appendit tae the base hook), ChildContent, an [Parameter(CaptureUnmatchedValues = true)] attribute splattin sae onie extra HTML attribute passes through.

Step 3 — Compose a wee 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 taks the visible Label as its ain parameter raither than a nestit label component — pass it ance, on Field, an TextInputon Label supplies the accessible name.

Verified 2026-08-29: an earlier draft nestit a separate Label component inside Field, which renders twa labels (ane empty) because Field aareadies renders its ain frae the Label parameter — catcht bi checkin the component source; Form haes nae Label parameter o its ain, sae the earlier draft's uiss o <Form> wis correctit bi omittin ane raither than inventin an attribute that disnae exist. The contact-form composed route is the same pattern, exercised bi real e2e an axe tests.

Check yer wirk: rin the app an the form looks plain — exactly as intendit: styling is still aheid. Press Tab: focus muives through ilka control, the label is announced, an submit wirks. The behaviour is deen; the look is yours, an it's neist.

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 aw wirkin

The Blazor Web example app shaws the full catalog styled an demoed, wi 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

Neist steps

Questions alang the wey? The help page is fu o answers — an if a step could be clearer, tell us an we'll gladly improve the tutorial.