WssBlazorControls 10.3.0

dotnet add package WssBlazorControls --version 10.3.0
                    
NuGet\Install-Package WssBlazorControls -Version 10.3.0
                    
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="WssBlazorControls" Version="10.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="WssBlazorControls" Version="10.3.0" />
                    
Directory.Packages.props
<PackageReference Include="WssBlazorControls" />
                    
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 WssBlazorControls --version 10.3.0
                    
#r "nuget: WssBlazorControls, 10.3.0"
                    
#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 WssBlazorControls@10.3.0
                    
#: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=WssBlazorControls&version=10.3.0
                    
Install as a Cake Addin
#tool nuget:?package=WssBlazorControls&version=10.3.0
                    
Install as a Cake Tool

WssBlazorControls

NuGet Version NuGet Downloads

A comprehensive library of form controls for Blazor applications providing consistent, feature-rich input components with built-in validation, accessibility support, and flexible styling options.

Features

  • Rich Form Controls: String, Number, Date, Boolean, Select, Radio, Checkbox lists, and TextArea components
  • Searchable & Multi-Select: AntDesign-style EditSelectSearch / EditMultiSelect — type-to-search, tags, virtualized dropdown
  • AntDesign-style UI Kit: dependency-free Alert, Modal, Drawer, Table, Pagination, Popover, Popconfirm, Skeleton, and toasts
  • Data Annotations Integration: Full support for validation attributes (Required, Range, MinLength, etc.)
  • Accessibility First: ARIA attributes, screen reader support, and keyboard navigation
  • Flexible Display Modes: Edit mode and read-only views for all controls
  • Consistent Styling: CSS classes and customizable appearance
  • TypeScript/JavaScript Interop: Enhanced client-side functionality
  • Cross-Platform: Works with both Blazor Server and Blazor WebAssembly

Installation

Install the package via NuGet Package Manager:

dotnet add package WssBlazorControls

Or via Package Manager Console:

Install-Package WssBlazorControls

Quick Start

  1. Add the using statement to your _Imports.razor:
@using Controls
  1. Include the CSS in your App.razor or index.html:
<link href="_content/WssBlazorControls/edit-controls.css" rel="stylesheet" />

<link href="_content/WssBlazorControls/wss-controls.css" rel="stylesheet" />
  1. Use the controls in your Blazor components:
@using System.ComponentModel.DataAnnotations

<EditForm Model="@model" OnValidSubmit="@HandleSubmit">
    <DataAnnotationsValidator />
    
    <EditString @bind-Value="model.Name" 
                Field="@(() => model.Name)"
                Label="Full Name" 
                IsRequired="true" />
    
    <EditNumber @bind-Value="model.Age" 
                Field="@(() => model.Age)"
                Label="Age" 
                IsRequired="true" />
    
    <EditDate @bind-Value="model.BirthDate" 
              Field="@(() => model.BirthDate)"
              Label="Birth Date" />
    
    <EditBool @bind-Value="model.IsActive" 
              Field="@(() => model.IsActive)"
              Label="Active Status" />
    
    <button type="submit">Submit</button>
    
    <ValidationSummary />
</EditForm>

@code {
    private PersonModel model = new();
    
    private void HandleSubmit()
    {
        // Handle form submission
    }
    
    public class PersonModel
    {
        [Required]
        [StringLength(100)]
        public string Name { get; set; } = "";
        
        [Required]
        [Range(1, 120)]
        public int? Age { get; set; }
        
        public DateTime? BirthDate { get; set; }
        
        public bool IsActive { get; set; } = true;
    }
}

Available Controls

Input Controls

  • EditString - Text input with masking and URL support
  • EditTextArea - Multi-line text input
  • EditNumber - Numeric input with validation
  • EditDate - Date picker component
  • EditBool - Checkbox for boolean values
  • EditBoolNullRadio - Three-state radio for nullable booleans

Selection Controls

  • EditSelect - Dropdown selection for objects
  • EditSelectEnum - Dropdown for enum values
  • EditSelectString - Dropdown for string values
  • EditSelectSearch - Searchable single-select (AntDesign-style: type-to-search, clear, virtualized)
  • EditMultiSelect - Multiple / tags select bound to a List<T> (AntDesign-style)
  • EditRadio - Radio buttons for objects
  • EditRadioEnum - Radio buttons for enums
  • EditRadioString - Radio buttons for strings

Multi-Selection Controls

  • EditCheckedStringList - Checkbox list for strings
  • EditCheckedEnumList - Checkbox list for enums

Support Components

  • FormLabel - Consistent labeling with tooltips and descriptions
  • FieldValidationDisplay - Validation message display
  • ReadOnlyValue - Read-only value presentation
  • EditDisplay - Static label+value pair (no model binding)
EditDisplay vs ReadOnlyValue

Both render text in the edit-readonly-value style, but their use cases are different:

EditDisplay ReadOnlyValue
When to use Standalone label+value pair outside an Edit* control — e.g. a derived value like "15.3 oz / can" that's not bound to a model property Always — it's rendered by the Edit* controls in read-only mode, not typically used directly by consumers
Owns its label Yes (Label, Description, Tooltip parameters) No — sits inside an Edit* control that owns the FormLabel
Model binding None None (reads Text after the parent has formatted the value)
Validation None None (the parent control's FieldValidationDisplay handles it)

Reach for EditDisplay when you want the same visual treatment as a read-only EditString but without an EditForm / model property behind it.

UI Kit (non-form) controls

A set of dependency-free, AntDesign-style general UI widgets (ported from Standalone.Controls). Unlike the Edit* controls these are not form-bound — they're plain components. They use the wss- CSS prefix and --wss-* theme tokens shipped in wss-controls.css (link it as shown in Quick Start). No service registration is required.

  • Select<T> - The dropdown engine behind EditSelectSearch / EditMultiSelect; usable standalone (single / multiple / tags, search, virtualized)
  • Alert - Contextual message banner (success / info / warning / error, closable, description)
  • Skeleton - Loading placeholder with shimmer; announces role="status" / aria-busy with a visually-hidden LoadingText (default "Loading") for screen readers
  • Popover - Click-triggered popover (4 placements)
  • Pagination - Controlled pager
  • Modal - Dialog with @bind-Visible, footer, mask-close
  • Drawer - Slide-in panel (4 placements)
  • Popconfirm - Inline confirm popover
  • Table<TItem> - Data table with Column / PropertyColumn / ActionColumn, row selection, paging (pager placement via PagerPosition = Top/Bottom/Both and alignment via PagerAlign), and column sorting (Sortable="true" on a PropertyColumn — non-comparable types degrade to non-sortable; or a SortBy comparison on any column). Columns may be conditionally rendered (@if)
  • Toasts & notifications - two paths with identical rendering: scoped / Server-safe (IMessageService / INotificationService via builder.Services.AddWssControlsToasts() + <MessageContainer /> / <NotificationContainer />), or registration-free static for WASM (WasmMessageService / WasmNotificationService + <WasmMessageContainer /> / <WasmNotificationContainer />). On Blazor Server use the scoped path — the static Wasm* services hold process-static state that would bleed across users.

Icon, Button, Checkbox, and Tag are intentionally not part of this library.

Server-side paging (Table)

The Table's built-in pager (PageSize) is in-memory — it materializes the whole DataSource and slices it client-side, so it can't reflect a server-side total. For server-side paging, compose the Table with the standalone, fully-controlled Pagination: give the Table only the current page (omit PageSize so it renders exactly what you pass), and drive a Pagination yourself.

<Table TItem="Row" DataSource="_pageRows">
    <PropertyColumn TItem="Row" TProp="int" Title="Id" Property="@(r => r.Id)" />
    <PropertyColumn TItem="Row" TProp="string" Title="Name" Property="@(r => r.Name)" />
</Table>

<div style="display:flex; justify-content:flex-end; margin-top:16px;">
    <Pagination Total="_total" PageSize="PageSize" Current="_page" CurrentChanged="GoToPageAsync" />
</div>

@code {
    const int PageSize = 20;
    List<Row> _pageRows = new();
    int _total, _page = 1;

    protected override Task OnInitializedAsync() => GoToPageAsync(1);

    async Task GoToPageAsync(int page)
    {
        _page = page;
        var result = await Api.GetRows(page, PageSize /*, sortField, sortDir */);
        _pageRows = result.Items.ToList(); // a NEW reference — the Table only re-copies when DataSource changes ref
        _total    = result.TotalCount;     // the server's overall count drives the pager
    }
}

Pagination is a controlled component (Total / Current / PageSize + CurrentChanged), so it shows the correct page count from the server total and raises CurrentChanged when the user picks a page. Handle sorting the same way — pass the sort field/direction into your request rather than using the Table's built-in Sortable, which only orders the page already loaded. A runnable example (with a simulated server) is in the /uikit gallery.

Component Features

All form controls implement the IEditControl interface and provide:

  • Identity Management: Id, IdPrefix for unique identification
  • Display Control: IsEditMode, IsDisabled, IsHidden
  • Labeling: Label, Description with markup support
  • Styling: ContainerClass for custom CSS
  • Validation: IsRequired integration with DataAnnotations
  • Conditional Display: Hiding modes and HidingMode enum

Examples

<EditSelectEnum @bind-Value="model.Priority" 
                Field="@(() => model.Priority)"
                Label="Priority Level" 
                IsRequired="true" />

@code {
    public enum Priority
    {
        Low,
        Medium,
        High,
        Critical
    }
    
    public class TaskModel
    {
        [Required]
        public Priority? Priority { get; set; }
    }
}

Radio Button Group

<EditRadioString @bind-Value="model.Department" 
                 Field="@(() => model.Department)"
                 Label="Department"
                 Options="@departments" />

@code {
    private List<string> departments = new() 
    { 
        "Engineering", 
        "Marketing", 
        "Sales", 
        "Support" 
    };
}

Checkbox List

<EditCheckedStringList @bind-Value="model.Skills" 
                       Field="@(() => model.Skills)"
                       Label="Technical Skills"
                       Options="@skills" />

@code {
    private List<string> skills = new() 
    { 
        "C#", 
        "JavaScript", 
        "Blazor", 
        "ASP.NET Core" 
    };
}

Styling and Customization

The library provides default styling through the included CSS file. You can customize the appearance by:

  1. Overriding CSS classes in your own stylesheets
  2. Using ContainerClass parameter for component-specific styling
  3. Applying custom CSS to the .edit-control-wrapper class

The AntDesign-style UI-kit controls (Alert, Modal, Table, Select, ...) are themed via --wss-* CSS custom properties in wss-controls.css. They default to the AntDesign 4.x look and bridge to your existing --color-primary / --color-danger / --border-color where those are defined, so they pick up your theme automatically. Override any --wss-* variable to re-theme.

<EditString @bind-Value="model.Name" 
            Field="@(() => model.Name)"
            Label="Name" 
            ContainerClass="my-custom-style" />

Accessibility

WssBlazorControls is built with accessibility as a priority:

  • ARIA attributes for screen readers
  • Keyboard navigation support
  • Focus management and indicators
  • Semantic HTML structure
  • High contrast color support

Browser Support

  • Modern browsers with WebAssembly support
  • Designed for both Blazor Server and Blazor WebAssembly scenarios
  • Compatible with .NET 8.0+

Contributing

Contributions are welcome! Please feel free to submit issues, feature requests, or pull requests.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Support

  • Documentation: Check the demo applications in the repository
  • Issues: Report bugs via GitHub Issues
  • Feature Requests: Submit enhancement requests via GitHub Issues

Changelog

10.3.0

New: EditFile — multi-file upload control

  • EditFile is a new form control that binds a List<IBrowserFile> via the standard Value / ValueChanged / Field pattern, integrating with EditContext validation like every other Edit* control.
  • Supports drag-and-drop and click-to-browse. An invisible <InputFile> overlay covers the entire drop zone so both interactions work natively without extra JS.
  • Multiple files are supported. The drop zone stays visible until an optional MaxFiles cap is reached; files already chosen appear as a dismissible list below it (hover to reveal the remove button per file).
  • AllowedExtensions (e.g. ".pdf", ".xlsx") filters by extension; MaxFileSizeBytes caps individual file size (default 10 MB). Validation errors from either check are shown inline below the drop zone.
  • The drop zone border turns red when there's a validation error from the format/size check or when the field fails EditContext validation; the upload icon switches to its error (red) variant to match.
  • Read-only mode shows the selected filenames with a paperclip icon; empty renders a blank ReadOnlyValue consistent with the other controls.
  • Styled to match the Hatch / Spot drop-zone look: dashed #b7b7b7 border, #f3f3f3 background, primary-color hover border. Tokens bridge to --color-primary and --color-danger so the control follows the consumer's theme.
  • Adds four inline-SVG icon classes to edit-controls.css: .edit-icon-upload, .edit-icon-upload-error, .edit-icon-paperclip, .edit-icon-delete.

Table — robust dynamic columns + graceful sort

  • Columns may now be conditionally rendered (@if). The Table re-collects its columns in document order on each render, so a hidden column drops out and a re-shown one returns to its declared position — previously a removed column left a stale header and cells behind, and re-showing it produced a duplicate. Hiding the column that drives the active sort now clears the sort so the indicator and the row order can't disagree.
  • A Sortable PropertyColumn whose property type isn't comparable no longer throws on the first header click (which on Blazor Server tore down the circuit) — the header simply isn't made sortable. Supply a SortBy comparison to sort any type.
  • A sortable column declared without a Title now gives its sort <button> an aria-label="Sort", so it isn't an unnamed button for screen-reader users.

Accessibility

  • Skeleton announces its loading state to screen readers: role="status" + aria-busy="true" and a visually-hidden LoadingText (default "Loading"); the placeholder bars are aria-hidden. New .wss-sr-only utility class.
  • Toast (Message) and Notification containers route each toast by severity into two always-present live regions — a polite role="status" region and an assertive role="alert" region — instead of flipping a single shared region's politeness when an error arrives (a change screen readers don't reliably re-announce, which could swallow the error). The regions are display:contents, so the on-screen layout is unchanged (errors group below the polite toasts).

10.2.0

Headline release: debuts the dependency-free AntDesign-style UI-kit controls (Select, Alert, Modal, Drawer, Table, Pagination, Popover / Popconfirm, Skeleton, toasts) and the searchable form selects (EditSelectSearch / EditMultiSelect), alongside a library-wide accessibility & architecture overhaul (the EditControlBase refactor). Adds Table column sorting and configurable pager placement. Includes one breaking dependency change — see below.

New: Table column sorting

  • Columns can now sort. Set Sortable="true" on a PropertyColumn (the comparison is derived from its Property via Comparer<T>.Default), or supply a SortBy comparison on any Column for custom / template columns. Clicking a sortable header cycles ascending → descending → unsorted (restoring the original DataSource order); the sort is stable (ties keep their original order). Headers expose aria-sort (ascending / descending / none) and a keyboard-focusable <button> so the feature is screen-reader- and keyboard-accessible. Sorting resets to page 1 and survives a DataSource swap.

Table / Pagination polish

  • The table pager is now configurable: PagerPosition="Top | Bottom | Both" (default Bottom) places it above, below, or both above and below the table, and PagerAlign="Left | Center | Right" (default Right, matching AntD) aligns it horizontally. When Both, the two pagers stay synced to the same page.
  • The pager buttons now hold a consistent 32px square via a min-height floor, so an aggressive consumer reset such as button { max-height: fit-content } can no longer collapse them to content height (which made the icon-only prev/next buttons render shorter than the numbered ones).
  • The Table now renders its grid and pager inside a single root element, so a parent's flex/grid gap doesn't stack on top of the pager's margin and inflate the space between the table and its pager.

Accessibility, theming & performance (audit follow-up)

  • Grouped controls now surface validation state. The radio controls (EditRadio, EditRadioEnum, EditRadioString, EditBoolNullRadio) expose aria-invalid / aria-required / aria-describedby on a role="radiogroup" <fieldset> named by its legend (previously splatted onto <InputRadioGroup>, which renders no element — so they didn't reliably appear). The checkbox lists (EditCheckedStringList, EditCheckedEnumList) mark each checkbox aria-invalid (they had none). And because the list controls are ComponentBase (not InputBase), they now subscribe to the EditContext so their invalid state updates live on validation — matching the scalar controls. This completes "aria-invalid on every editable control".
  • aria-describedby no longer dangles — it references only the desc- / tooltip- ids that actually render, and is resolved once per control rather than re-interpolated on every render. aria-errormessage is emitted only while the field is invalid (per the ARIA spec).
  • Form controls are self-sufficient out of the box. edit-controls.css now ships a :focus-visible ring for the editable elements (WCAG 2.4.7 — no longer dependent on the browser default the consumer may have reset) and an .invalid border, so keyboard focus and the validation error state are visible without the consumer supplying their own styles. The validation X icon and the tooltip info icon use currentColor driven by --color-danger / --color-text, so they follow the consumer theme.
  • wss-controls.css: the Select sizing now uses the existing --wss-* tokens (overriding a token rescales the control as intended), and the classes the markup referenced but the stylesheet never defined (wss-popconfirm-title, wss-table-caption, wss-select-selection-item-rest, …) are now declared.
  • Fewer per-render allocations. Select caches its visible tags and Table caches the current page (it was materializing the page twice per render). Table now treats DataSource / SelectedItems as immutable parameters (reference-guarded) — reassign them to refresh rather than mutating in place.
  • Removed the unused ReadOnlyValue.IsRequired parameter (it was required but never rendered).
  • Nullable enum selects can represent null. EditSelectEnum<TEnum?> now renders a leading empty/placeholder option (label via the new NullOptionText parameter) so a null value shows blank instead of silently displaying the first member, and the user can clear the field. Non-nullable enums are unchanged.
  • More ARIA correctness. All bool-bound ARIA booleans (aria-expanded / aria-hidden / aria-disabled) now render lowercase "true"/"false"; Alert announces by severity (role/aria-live: error = assertive, otherwise polite) instead of always role="alert"; the radio <fieldset> itself is the role="radiogroup" (no nested double-group) with its id gated to edit mode so it doesn't collide with the read-only value; read-only aria-labelledby is suppressed when the label is hidden; the Select gets a focus ring before it opens; and Escape closes Popover / Popconfirm from inside the panel.
  • Correctness fixes. Select now shows the selected label and clear button even when a single value equals default(TValue) (e.g. a non-nullable enum's 0 member — previously mis-rendered as the empty placeholder); ValidationView summary links now target each control's actual id, honoring IdPrefix / an explicit Id (the resolved id is captured at field registration) instead of a recomputed guess; the checkbox lists no longer throw in read-only mode when the bound list is null, and sanitize their read-only per-option ids via ToId(); a disabled Popconfirm trigger is now aria-disabled and removed from the tab order.
  • More correctness & a11y fixes. EditRadioString now follows an externally-changed value (form reset, async-loaded model, programmatic set) instead of caching the selection once — and a custom initial value correctly resolves to the "Other" radio with its text box pre-filled; EditRadioEnum's "Other" free-text input gained an accessible name (aria-label), matching its EditRadioString sibling; the Select clear button is now revealed on keyboard focus (:focus-within), not only on hover, so a keyboard user can see the control they've tabbed to; the Table's "select all" checkbox enters the native indeterminate (mixed) state when only some rows on the page are selected, so screen readers announce the partial selection; and the length-attribute helper takes the tighter (smaller) upper bound when both [StringLength] and [MaxLength] apply.
  • Checkbox-list validation links resolve. EditCheckedStringList / EditCheckedEnumList now render their resolved id on the <fieldset> in edit mode (gated like the radio groups), so a ValidationView summary link for one of these fields actually jumps to the control — their checkboxes/label/error elements all carry decorated ids, so the bare id previously had nowhere to land, leaving the link dangling.
  • Visual & robustness fixes. Pagination clamps an out-of-range Current to the valid range, so Previous/Next enable correctly instead of looking clickable but doing nothing; a long Popconfirm title now wraps inside the panel instead of overflowing it; and the loading Skeleton shows a flat fill under prefers-reduced-motion rather than a frozen, off-centre shimmer band.
  • Overlay focus-trap & scroll-lock hardening. The Modal / Drawer focus trap no longer lets Shift+Tab escape when focus is on the panel itself (e.g. after clicking an empty area of the body) — focus is pulled back into the dialog. The body-scroll lock is now ref-counted, so stacked overlays don't unlock the page when the first-opened one closes, and the focus handle's disposal is idempotent.

New: AntDesign-style controls (ported from Standalone.Controls)

  • Form selects: EditSelectSearch<T> (searchable single-select) and EditMultiSelect<T> (multiple / tags, binds List<T>) — full Edit* controls (validation, label, read-only, FormOptions) backed by a new dependency-free, virtualized dropdown engine (Select<T>). They sit alongside the existing EditSelect / EditSelectEnum / EditSelectString, which are unchanged.
  • UI kit (non-form): Select<T>, Alert, Skeleton, Popover, Pagination, Modal, Drawer, Popconfirm, Table<TItem> (+ Column / PropertyColumn / ActionColumn), and toasts/notifications in two flavors — scoped/Server-safe (IMessageService / INotificationService via AddWssControlsToasts() + MessageContainer / NotificationContainer) and registration-free static for WASM (WasmMessageService / WasmNotificationService + their containers). Icon, Button, Checkbox, and Tag were intentionally excluded.
  • New stylesheet: these controls use the wss- class prefix and --wss-* theme tokens shipped in wss-controls.css. Add a second link alongside edit-controls.css:
    <link href="_content/WssBlazorControls/wss-controls.css" rel="stylesheet" />
    
    Tokens default to the AntDesign 4.x look and bridge to your existing --color-* / --border-color where present. The Select keyboard helper ships as an RCL JS module at _content/WssBlazorControls/wss-select.js (auto-imported, degrades gracefully).
  • No service registration required (consistent with the rest of the library).

Accessibility & correctness (library audit)

  • Modal / Drawer: trap focus while open, restore focus to the trigger on close, close on Escape, lock body scroll, and expose role="dialog" + aria-modal="true" + aria-labelledby (the title). OK/confirm still never auto-closes — the caller decides.
  • Popover / Popconfirm: the trigger is a real focusable control (role="button", tabindex="0", aria-haspopup, aria-expanded) operable from the keyboard — Enter / Space to open, Escape to close. Both flip to the opposite side and shift along the cross axis to stay within the viewport, rendering hidden for one frame so the placement is never seen to jump.
  • Select / EditSelectSearch / EditMultiSelect: full combobox ARIA (role="combobox" / listbox / option, aria-expanded, aria-controls, aria-activedescendant); the dropdown now opens upward when it would otherwise run off the bottom of the viewport.
  • Pagination: rewritten as a semantic <nav aria-label="Pagination"> of <button>s with aria-current="page" on the active page and aria-labels on the prev/next controls (was <ul> / <li> / <a>).
  • Toasts / notifications: the live region is announced via role="status" + aria-live="polite".
  • ReadOnlyValue now HTML-encodes the value it displays instead of rendering it as raw markup — bound user data can no longer inject markup.
  • EditDate read-only formats the bound value by its own type with DateFormat. The old code round-tripped through the editor string, which could shift the date across midnight in non-UTC zones and rendered a TimeOnly as a date; an incompatible format now degrades to the value's own ToString rather than throwing.
  • EditCheckedEnumList / EditCheckedStringList build a new list when toggling instead of mutating the caller's bound collection in place.
  • The placement enum for Popover / Popconfirm is named PopupPlacement (it positions popups, not tooltips). The library builds with 0 warnings across net8 / net9 / net10.

Breaking dependency change

  • Removed Microsoft.AspNetCore.Components.DataAnnotations.Validation (3.2.0-rc1) from the WssBlazorControls package — the library itself never used it. Consumers who use <ObjectGraphDataAnnotationsValidator> or the [ValidateComplexType] attribute for nested-object validation must now add the package to their own project:
    dotnet add package Microsoft.AspNetCore.Components.DataAnnotations.Validation --version 3.2.0-rc1.20223.4
    
    This eliminates the prerelease-dependency warning that previously bled through to consumer builds.

Behavior

  • Validation messages now respect the Label parameter override on every control. Previously only EditCheckedStringList and EditCheckedEnumList passed Label through to FieldValidationDisplay; the other 12 controls would still derive the label from the model's attribute. Now if you set <EditString Label="Username" ... />, the validation message shows "Username is required" instead of falling back to the property name.
  • EditSelectString <option> elements now render the title tooltip (consistent with EditSelectEnum).
  • Cosmetic: EditDate's ReadOnlyValue now uses @_id / @_isRequired like every other control.

Build / packaging

  • <GeneratePackageOnBuild> is now scoped to Configuration == Release. Dev / inner-loop builds no longer regenerate .nupkg files on every save — dotnet pack -c Release -o ./nupkg continues to produce them on demand.
  • Package now ships with a 128×128 icon (icon.png, white "W" on Blazor purple). Visible in NuGet listings and Visual Studio's Manage NuGet Packages dialog.

New shared CSS class

  • .edit-input is now applied to every editable element (<input>, <textarea>, <InputSelect>, <InputDate>) across EditString, EditNumber, EditDate, EditTextArea, EditSelect, EditSelectString, EditSelectEnum, plus the "Other" text inputs in EditRadioString / EditRadioEnum. The bundled edit-controls.css ships an empty rule — consumers can now style every editable element with one selector instead of writing per-element CSS for input / textarea / select separately. Per-control classes (.edit-string-input, .edit-textarea-input, .edit-select-select, etc.) remain available for fine-tuning.

Internal

  • HidingMode: dropped the meaningless explicit = 1, 2, 3, 4, 5 numeric values. Default is now 0 (None) which matches the ?? HidingMode.None fallback already in every control. Consumers don't notice unless they were persisting the enum as an int — in which case existing values shift down by 1.
  • ValidationHelper: replaced the brittle message.Split(' ') + hardcoded array-index parsing of Range messages with a compiled regex. Now tolerates multi-word field names ("Order Total") and small format variations. Type-min/max sentinel detection moved into HashSet<string> lookups instead of a long || chain.

Architecture: EditControlBase<TValue>

  • 11 of 14 controls now inherit a single EditControlBase<TValue> : InputBase<TValue>, IEditControl instead of inheriting one of Microsoft's specialized Input* classes (InputText / InputNumber / InputDate / InputCheckbox / InputSelect / etc.). The base hoists every IEditControl parameter, both cascading parameters, the protected derived state (_id, _isRequired, _attributes, _fieldIdentifier), and the ShowEditor / ShouldHideLabel checks — so each derived control's .razor.cs shrinks to just its component-specific parameters + parser + helpers. Net ~430 lines removed across the 11 controls.
  • The string-input/textarea/number/date/select parsing logic that Microsoft's Input* classes used to provide is now ported into each control (typically a 5-15 line TryParseValueFromString override that delegates to BindConverter). Behavior is preserved — the new parsers route through the same BindConverter Microsoft uses internally.
  • EditCheckedStringList and EditCheckedEnumList migrated to a sibling EditControlListBase<TItem> (different shape — binds List<TItem> instead of a scalar). The SetAsync(item) rename to ToggleAsync(item) is the only consumer-facing surface change.
  • EditRadio is the one remaining control still on Microsoft's InputRadioGroup<T> — it depends on the cascading-context plumbing that <InputRadio> children consume, and replacing the group requires also replacing the public <InputRadio> API. Intentional.
  • _Imports.razor now exposes Microsoft.AspNetCore.Components.Forms and Controls.Helpers so individual razor files no longer need per-file @using directives for <InputRadioGroup> / <InputRadio> / .ToId() / etc.

Tests

  • FormTesting/FormTesting.Client.Tests/ (xUnit + bUnit, multi-targeted net8/9/10) — 270 tests (run once per TFM) covering the helpers (EnumHelpers cache + attribute precedence, AttributesHelper.GetId / GetLabelText / GetMinAndMaxLengths, EditControlInit, ValidationHelper regex parsing), bUnit smoke tests for the form controls (rendered DOM, ARIA, edit/read-only switching), the AntDesign-style selects, and the UI-kit widgets (Table, dialogs, toasts) — plus regression tests for the audit fixes (ReadOnlyValue HTML-encoding, EditDate read-only formatting, checked-list immutability). Run with dotnet test FormTesting/FormTesting.Client.Tests/FormTesting.Client.Tests.csproj.
  • FormTesting/FormTesting.Client.E2ETests/ (xUnit + Playwright .NET, net10) — a 67-test end-to-end suite (one class per Edit* control plus the searchable selects and a driver for the /uikit gallery) with committed visual-regression baselines. Run with dotnet test FormTesting/FormTesting.Client.E2ETests/FormTesting.Client.E2ETests.csproj.

10.1.0

Behavioral changes (read before upgrading)

  • EditBool: read-only mode now renders ReadOnlyValue with the new TrueText / FalseText parameters (default "Yes" / "No"), matching every other control. Set RenderAsCheckboxWhenReadOnly="true" to keep the legacy disabled-checkbox display.
  • EditString: aria-required now reflects the actual required state instead of being hard-coded to "true".

New CSS class — required for the invalid-icon overlay

  • .edit-input-with-icon wraps <input> / <textarea> / <InputDate> together with the optional red-X invalid icon in EditString, EditNumber, EditDate, EditTextArea. The bundled edit-controls.css ships an empty hook (the icon overlays the input via .edit-icon-invalid's negative margin and needs no positioning here). If you have your own stylesheet, no changes are required unless you want to adjust the input row's layout.

New parameters

  • EditBool.TrueText (default "Yes")
  • EditBool.FalseText (default "No")
  • EditBool.RenderAsCheckboxWhenReadOnly (default false)

New components / helpers

  • <InvalidIcon CssClass="..." /> — reusable red-X SVG, conditional on the host's CssClass containing "invalid".
  • EditControlInit (in Controls.Helpers) — static helper that consolidates the OnInitialized setup and the ShowEditor / ShouldHideLabel checks every control was duplicating.

Markup consistency

  • EditSelectEnum switched from @bind:get / @bind:set to <InputSelect @bind-Value=...> so it matches EditSelect / EditSelectString.
  • EditBoolNullRadio radio inputs now carry aria-required, aria-invalid, aria-describedby, and aria-errormessage. (Moved to a group-level role="radiogroup" container in the next release — see Unreleased.)
  • aria-invalid is now rendered on every scalar editable control. (The grouped radio / checkbox-list controls are brought to parity in the next release — see Unreleased.)
  • .ToId() is now applied to enum option ids in EditSelectEnum and EditRadioEnum — fixes invalid HTML ids when an enum's display name contains spaces or punctuation.
  • The red-X invalid icon (previously only on EditString) now appears on EditNumber, EditDate, and EditTextArea as well.

Performance

  • EnumHelpers._nameCache is now a thread-safe ConcurrentDictionary<(Type, string), string> keyed by enum type — fixes potential cross-type collisions and removes a thread-safety hazard on pre-rendering.
  • EnumHelpers.GetName now honors both [EnumDisplayName] and [Display(Name=...)]. Previously [Display] only affected sort order and [EnumDisplayName] only affected display, so the two could disagree.
  • The reflection-heavy enum sort blocks in EditSelectEnum / EditRadioEnum / EditCheckedEnumList collapsed to OrderBy(x => x.GetName()) and benefit from the cache.

Bug fixes

  • Fixed package description typo (HierarchyAndEmployeeRecordproviding artifact).
  • Removed stray IsRequiredChanged parameter that existed only on EditRadioEnum.
  • EditCheckedStringList was silently dropping the IdPrefix parameter (null was being passed instead). Now consistent with every other control.
  • EditBoolNullRadio false-radio's class attribute incorrectly used @ContainerClass instead of @CssClass.
  • focusFirstInvalidField (JS) now correctly handles invalid wrapper elements that aren't form fields, includes <select>, and guards .select() for input types that don't support it.

Refactoring (internal)

  • All 14 controls now call EditControlInit.Init(...) in OnInitialized instead of duplicating the same 4 lines.
  • All controls use EditControlInit.ShowEditor(...) and EditControlInit.ShouldHideLabel(...) for the visibility checks.
  • JavaScript helpers namespaced under window.WssEditControls.*. Legacy window.focusFirstInvalidField / window.log / etc. are still exposed for back-compat — safe to migrate at your own pace.
  • JsInteropEc.FocusFirstInvalidField uses Task.Yield() instead of Task.Delay(1).
  • FormLabel._isRequired changed from string ("true"/"false") to bool.
  • IEditControl.IsDisabled doc comment fixed (was "Not used" despite being used by every control).
  • Deleted dead ExampleJsInterop.cs template code.
  • Removed unused EditCheckedStringList.hasError and ReadOnlyValue._emptyValue fields.
  • Build warnings reduced from 87 → 57.

10.0.7

  • EditString: Add Autocomplete parameter (defaults to "one-time-code") to prevent browser extensions and autofill from intercepting Blazor input events on fields with IDs containing keywords like "email"

10.0.2

  • Support .net 8,9,10

10.0.1

  • Upgrade to .net 10
  • Add the ability to hide the required star within FormOptions
  • Changed editControls.js to edit-controls.js

1.13.8

  • Exposed xmldoc comments

1.13.7

  • refactoring

1.13.6

  • Move the star for non-legends to the left.

1.13.5

  • Enable tooltips through markup
  • Move the required star to the left of the label

1.13.4

  • EditDate and other controls. Add a null value string to display when the value is null, such as a dash instead of blank space.
  • IsRequired parameter on all controls. When set forces the “edit-label-required-star” to show up without being required in the DataAnnotations.
  • Accessibility updates for EditCheckedStringList

1.13.3

  • Current stable release
  • Full feature set with comprehensive validation support

1.0.13.2

  • EditCheckedListEnum

1.0.13.1

  • Rename icons to have edit- in front of the current names
    • .icon-eye ⇒ .edit-icon-eye
  • Icon-invalid, icon-eye-invisible
  • EditSelectEnum no longer requires specifying the type.
  • Tooltips exist on the controls
  • Only from attributes right now [Tooltip(“My cool tooltip”)

1.0.12.11

  • Import js into application in App.razor or index.html
    • <script src="_content/WssBlazorControls/editControls.js"></script>
      
    • This is to add the functionality of “When submit is clicked, but invalid, scroll to the first input that is invalid.
    • Use JsInteropEc to access js methods. Use JsInteropEc.FocusFirstInvalidField() when there are validation errors while submitting.
  • EditCheckedStringList
    • Error message shows up on each checkbox

1.0.12.10

  • IsRequired parameter on all controls. When set forces the “edit-label-required-star” to show up without being required in the DataAnnotations.
  • Accessibility updates for EditCheckedStringList

1.0.12.x

  • moved away from utilizing bootstrap css classes such as form-group to using classes that start with edit- to avoid conflicts with other libraries
  • New Features
  • IsHidden to hide controls withougt wrapping them in an if statement
  • Hiding allows hiding controls based on their own property for [Never, WhenReadonlyAndNull, WhenReadonly, etc.]
    • This also exists within FormOptions, so the hiding can be controlled over a large group of controls.
  • Control Changes
  • EditRadio and EditCheckedList
    • Change parameter from HasHorizontalButtons → IsHorizontal
    • Removed the need for "Type" parameter, now uses the type of the value passed in.
  • EditSelectEnum
    • Removed the need for "Type" parameter, now uses the type of the value passed in.
  • New Controls
    • EditBoolNullRadio
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  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 (1)

Showing the top 1 NuGet packages that depend on WssBlazorControls:

Package Downloads
WssBlazorControls.Demo

Demo components for WssBlazorControls, showcasing usage of each control with interactive examples.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
10.3.0 40 6/14/2026
10.0.7 238 3/16/2026
10.0.6 163 2/9/2026
10.0.5 77 2/9/2026
10.0.4 80 2/9/2026
10.0.3 74 2/9/2026
10.0.2 156 11/23/2025
10.0.1 167 11/23/2025
1.13.8 395 11/18/2025
1.13.7 271 10/29/2025
1.13.6 179 10/29/2025
1.13.5 193 10/28/2025
1.13.4 185 9/24/2025
1.0.13.3 442 9/18/2025
1.0.13.2 431 8/21/2025
1.0.13.1 187 8/19/2025
1.0.12.10 207 8/12/2025
1.0.12.9 172 8/12/2025
1.0.12.8 178 8/12/2025
Loading failed

10.3.0 - Adds EditFile (multi-file drag-and-drop upload control), Table dynamic-column robustness, graceful sort degradation, and accessibility improvements to Skeleton and toasts. See the README changelog for the full list.