NhsWales.BlazorKit.Components 0.2.2

dotnet add package NhsWales.BlazorKit.Components --version 0.2.2
                    
NuGet\Install-Package NhsWales.BlazorKit.Components -Version 0.2.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="NhsWales.BlazorKit.Components" Version="0.2.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="NhsWales.BlazorKit.Components" Version="0.2.2" />
                    
Directory.Packages.props
<PackageReference Include="NhsWales.BlazorKit.Components" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add NhsWales.BlazorKit.Components --version 0.2.2
                    
#r "nuget: NhsWales.BlazorKit.Components, 0.2.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package NhsWales.BlazorKit.Components@0.2.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=NhsWales.BlazorKit.Components&version=0.2.2
                    
Install as a Cake Addin
#tool nuget:?package=NhsWales.BlazorKit.Components&version=0.2.2
                    
Install as a Cake Tool

NhsWales.BlazorKit.Components

Blazor components that render the markup and CSS classes defined by the DHCW component library, so applications get consistent, accessible form controls without hand-writing the HTML or remembering the class names.

This library defines no styling of its own. Every class it emits comes from the component library.

Setup

The stylesheet ships as a static web asset. Reference it from your host page (App.razor), after any other framework CSS so its rules win:

<link rel="stylesheet" href="@Assets["_content/NhsWales.BlazorKit.Components/css/nhsw.css"]" />

@Assets[...] gives the fingerprinted URL, which MapStaticAssets serves as immutable. Hard-coding the raw path works but costs a revalidation round-trip on every page load.

Note the stylesheet sets global html/body typography and background — that is the design system's intent, not a leak, but it does restyle the whole page.

See NHSW-CSS-VERSION.md for the vendored version and how to update it.

Then make the components available, typically from _Imports.razor:

@using NhsWales.BlazorKit.Components

NhswTextInput

A single-line text input with a visible label, optional hint text, an optional error state, and the design system's fixed and fluid width variants.

<NhswTextInput Id="nhs-number"
               Label="NHS number"
               Hint="Enter the 10 digit NHS number"
               @bind-Value="Model.NhsNumber"
               MaxLength="10"
               Width="NhswInputWidth.Width10"
               InputMode="NhswInputMode.Numeric" />

renders

<div class="nhsw-form-group">
  <label class="nhsw-label" for="nhs-number">NHS number</label>
  <div class="nhsw-hint" id="nhs-number-hint">Enter the 10 digit NHS number</div>
  <input class="nhsw-input nhsw-input--width-10" id="nhs-number" name="nhs-number"
         type="text" aria-describedby="nhs-number-hint" inputmode="numeric" maxlength="10">
</div>

Parameters

Parameter Type Notes
Id string Required. The input's id, the label's for, and the stem for the hint and error ids. Must not contain whitespace.
Name string? The submitted name. Defaults to Id.
Label string Required. The visible label.
Hint string? Hint text, rendered between the label and the input.
Value string? The current value.
ValueChanged EventCallback<string?> Enables @bind-Value.
MaxLength int? Rendered as maxlength. Values below 1 are ignored.
Width NhswInputWidth Width variant. Defaults to Full.
ErrorMessage string? When set, renders the component in its invalid state.
ErrorPrefix string? Visually hidden prefix on the error. Defaults to "Error:"; empty string omits it.
Disabled bool Renders the disabled attribute.
AutoComplete string? Rendered as autocomplete. Blank is ignored.
InputMode NhswInputMode Rendered as inputmode. Defaults to Default, which omits the attribute.

Any other attribute is splatted onto the <input>, so data-* hooks, required, readonly and event handlers such as @onblur work as usual:

<NhswTextInput Id="postcode" Label="Postcode" data-testid="postcode" @onblur="ValidateAsync" />

Attributes the control derives itself are ignored rather than allowed to overwrite it: id, name, class, aria-describedby, aria-invalid, and the value/onchange pair the binding owns. Splatting is emitted last, so without this a stray class would strip the NHS Wales styling — including the invalid state — and a stray aria-describedby would silently detach the hint and error.

Attributes are omitted entirely when their parameter is unset, matching the library's own markup.

Id and Label are validated at runtime and throw InvalidOperationException if missing — [EditorRequired] is only a build-time hint and does not fire for a bound null. This is deliberate: an input with no accessible name is the failure this library exists to prevent.

Widths

NhswInputWidth maps to the modifier classes in the library's _input.scss. Fluid widths are a proportion of the container and collapse to full width on small screens; fixed widths are sized to a number of characters.

Member Class
Full nhsw-input--full fluid
ThreeQuarters nhsw-input--three-quarters fluid
TwoThirds nhsw-input--two-thirds fluid
Half nhsw-input--half fluid
Third nhsw-input--third fluid
Quarter nhsw-input--quarter fluid
Width20 nhsw-input--width-20 fixed, 20 characters
Width10 nhsw-input--width-10 fixed, 10 characters
Width5 nhsw-input--width-5 fixed, 5 characters
Width4 nhsw-input--width-4 fixed, 4 characters
Width3 nhsw-input--width-3 fixed, 3 characters
Width2 nhsw-input--width-2 fixed, 2 characters

Width defaults to Full, so nhsw-input--full is always present. It is width 100%, the same as the base nhsw-input, so the default is visually identical to the library's plain class="nhsw-input" examples.

Input modes

NhswInputMode controls the inputmode attribute. Default omits it; None, Text, Decimal, Numeric, Tel, Search, Email and Url render the matching value.

Errors

<NhswTextInput Id="postcode" Label="Postcode" ErrorMessage="Enter a real postcode" />
<div class="nhsw-form-group nhsw-form-group--error">
  <label class="nhsw-label" for="postcode">Postcode</label>
  <p class="nhsw-error-message" id="postcode-error">
    <span class="nhsw-visually-hidden">Error:&#32;</span>Enter a real postcode
  </p>
  <input class="nhsw-input nhsw-is-invalid nhsw-input--full" id="postcode" name="postcode"
         type="text" aria-describedby="postcode-error" aria-invalid="true">
</div>

The form group gains the error modifier, the input gains nhsw-is-invalid and aria-invalid="true", and the message is referenced by aria-describedby. When both a hint and an error are present, aria-describedby lists the hint first, then the error, matching the library.

For Welsh-language pages, set ErrorPrefix="Gwall:" so the screen-reader prefix is not announced in English.

Accessibility

The component enforces the design system's rules rather than leaving them to the caller:

  • Every input has a visible <label> whose for matches the input's id.
  • Hint and error text are always connected via aria-describedby.
  • Invalid fields carry aria-invalid="true".
  • There is no placeholder parameter, so placeholder text cannot stand in for a label.

Ids are derived from Id, so each Id must be unique on the page. Repeated instances in a loop should carry @key.

Value binding

@bind-Value updates the bound property on the input's change event, when the field loses focus, matching Blazor's built-in InputText.

If the bound property normalises or rejects what the user typed, the field is updated to show the stored value. A property that strips spaces from an NHS number will visibly rewrite 123 456 7890 to 1234567890 on blur, and one that rejects a value outright will restore the previous one.

Who owns the value

Both controls behave the same way here, and which behaviour you get depends on whether you supply Value — not on whether you supply ValueChanged.

You supply Who owns the value What the user sees
@bind-Value (both) You Your value always wins, so normalising and rejecting both work
Value only You Your value is restored on the next render, overwriting any edit
ValueChanged only The control The edit is kept and reported to you
Neither The control The edit is kept, like a plain HTML control

The controls are controlled elements, so their next render overwrites whatever the browser holds. When you supply Value that is the point: a value you reject does not stay on screen. When you do not, there is nowhere to put the input, so the control keeps it rather than appearing to ignore the user.

An instance the renderer reuses for a different field drops any input it was holding, so repeated controls do not misattribute an answer — but give looped instances a @key anyway.

Scope

This component covers the parameters in the user story. The library's nhswInput macro also supports prefixes and suffixes, alternative input types, label sizes, and a live character count. Those are deliberately not implemented yet.

MaxLength renders only maxlength. The library's macro would also add a live character-count hint, which needs nhsw-behaviours.js; that behaviour is intentionally left out.

NhswSelect

A dropdown for choosing one option from a list. Generic over the bound value, so it binds directly to string, int, Guid, an enum, or their nullable forms.

The design system advises using a select only as a last resort in public-facing services — prefer radios for short lists.

<NhswSelect TValue="string"
            Id="region"
            Label="Choose region"
            Hint="Select the region where you are based"
            Items="Regions"
            @bind-Value="Model.Region"
            Width="NhswSelectWidth.Medium" />
private static readonly NhswSelectItem<string>[] Regions =
[
    new("", "Choose region"),
    new("north-wales", "North Wales"),
    new("mid-wales", "Mid Wales"),
    new("south-wales", "South Wales")
];

renders

<div class="nhsw-form-group">
  <label class="nhsw-label" for="region">Choose region</label>
  <div class="nhsw-hint" id="region-hint">Select the region where you are based</div>
  <select class="nhsw-select nhsw-select--m" id="region" name="region" aria-describedby="region-hint">
    <option value="">Choose region</option>
    <option value="north-wales">North Wales</option>
    <option value="mid-wales">Mid Wales</option>
    <option value="south-wales">South Wales</option>
  </select>
</div>

Parameters

Parameter Type Notes
Id string Required. The select's id, the label's for, and the stem for the hint and error ids. Must not contain whitespace.
Name string? The submitted name. Defaults to Id.
Label string Required. The visible label.
Hint string? Hint text, rendered between the label and the select.
Value TValue? The selected value.
ValueChanged EventCallback<TValue?> Enables @bind-Value.
Items IEnumerable<NhswSelectItem<TValue>> Required. The options, in order. Null renders an empty select.
Width NhswSelectWidth Width variant. Defaults to Default.
ErrorMessage string? When set, renders the component in its invalid state.
ErrorPrefix string? Visually hidden prefix on the error. Defaults to "Error:"; empty string omits it.
Disabled bool Disables the whole select.

Any other attribute is splatted onto the <select>, with the same reserved names as the text input plus one more: multiple is stripped. This component binds a single value, and the design system directs multi-select use cases to checkboxes instead.

NhswSelectItem<TValue>

Property Type Notes
Value TValue? The value bound when the option is selected.
Text string The text shown to the user.
Disabled bool When true, the option is rendered disabled and cannot be chosen.

Construct with new(value, text), optionally new(value, text, disabled: true), or with an object initialiser.

Generic values and placeholders

An item with a null value renders an option with no value attribute, which is what the library's macro does for placeholders. Use a nullable TValue so choosing it binds back as null:

<NhswSelect TValue="int?" Id="year" Label="Year of birth" Items="Years" @bind-Value="Model.Year" />
private static readonly NhswSelectItem<int?>[] Years =
[
    new(null, "Choose year"),
    new(1980, "1980"),
    new(1990, "1990")
];

For string, an empty-string value works equally well as a placeholder, as in the first example.

The selected value is resolved by matching what the browser submits back against the items you supplied, rather than by parsing it into TValue. That keeps any value type working, including ones no converter handles.

When no option matches the bound value — including when nothing is bound — no option is marked selected and the select carries no value, so the browser shows the first option. That is why a placeholder should come first in the list: for a non-nullable TValue such as a plain int or an enum, the default value usually matches no option, and the browser will display the first one while the bound property still holds the default.

Every option must be distinguishable by the string it submits. A TValue with no meaningful string form — a plain class, say — formats every option identically, so the component throws rather than silently resolving every selection to the first item. Use a record, an id, or an enum.

Widths

NhswSelectWidth maps to the modifier classes in the library's _select.scss. Each sets a min-width; the select still grows to fit its longest option, up to the width of its container.

Member Class Minimum width
Default (none) 12rem, from the base nhsw-select rule
Full nhsw-select--full fills the container
ExtraSmall nhsw-select--xs 4rem
Small nhsw-select--s 8rem
Medium nhsw-select--m 12rem
Large nhsw-select--l 16rem
ExtraLarge nhsw-select--xl 24rem

Default and Medium render the same width; they differ only in whether the modifier class is emitted.

Errors

Setting ErrorMessage produces the same invalid state as the text input: the error modifier on the form group, nhsw-is-invalid and aria-invalid="true" on the select, and the message referenced by aria-describedby. When a hint is also present, the hint is listed first.

Accessibility

As with the text input, the component enforces the design system's rules: a visible label whose for matches the select's id, hint and error text always connected via aria-describedby, and aria-invalid on invalid fields. Id and Label are required and validated at runtime.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.2.2 106 9/9/2026
0.1.1 95 9/8/2026
0.1.0 102 9/8/2026