MyBlazorUI 0.3.1
dotnet add package MyBlazorUI --version 0.3.1
NuGet\Install-Package MyBlazorUI -Version 0.3.1
<PackageReference Include="MyBlazorUI" Version="0.3.1" />
<PackageVersion Include="MyBlazorUI" Version="0.3.1" />
<PackageReference Include="MyBlazorUI" />
paket add MyBlazorUI --version 0.3.1
#r "nuget: MyBlazorUI, 0.3.1"
#:package MyBlazorUI@0.3.1
#addin nuget:?package=MyBlazorUI&version=0.3.1
#tool nuget:?package=MyBlazorUI&version=0.3.1
MyBlazorUI
MyBlazorUI is an original Blazor UI component framework built as a Razor Class Library. It uses .NET 10, nullable reference types, code-behind component logic, CSS isolation, design-token themes, and small JavaScript ES modules for browser behaviors that need JS interop.
The library is intentionally developed in phases. Phase 7 evolves the lightweight Grid into a stronger v3 foundation with row selection, text filtering, column width and visibility controls, density modes, improved paging, row actions, and application-managed state persistence while keeping advanced enterprise grid features deferred to future work.
Implemented Components
Phase 1:
MyButtonMyIconButtonMyTextBoxMyCheckBoxMyPopup
Phase 2:
MySelectMyToastService,MyToastProvider,MyToastContainerMyCardMyTabs,MyTabPageMyDropdownMyFormLayout,MyFormLayoutItem
Phase 4, Phase 5, and Phase 7:
MyGrid<TItem>MyGridColumn<TItem>- Single and multiple row selection
- Optional checkbox selection column
- Text filter row
- Column width, min width, max width, and visibility
- Compact, normal, and comfortable density modes
- Page size selector and pager summary
- Persistent grid state snapshot/apply foundation
- Grid state and server-side data request/result APIs
Architecture Principles
- Original implementation with stable, discoverable
My*public APIs. - Razor markup stays focused on rendering; component logic lives in
.razor.cspartial classes. - Component visuals use
.razor.cssisolation and shared CSS variables. - Static assets are served from
_content/MyBlazorUI/. - Browser behavior uses component-focused JS modules, not global scripts.
- Inputs integrate with
EditFormthroughInputBase<TValue>. - Public services are registered through
builder.Services.AddMyBlazorUI().
Solution Structure
src/
MyBlazorUI/
Components/
Button/
IconButton/
TextBox/
CheckBox/
Popup/
Select/
Toast/
Card/
Tabs/
Dropdown/
FormLayout/
Grid/
Core/
Data/
Services/
Interop/
Themes/
Utilities/
Extensions/
wwwroot/
themes/
js/
MyBlazorUI.Docs/
MyBlazorUI.Playground/
tests/
MyBlazorUI.BUnitTests/
Core/Data contains a minimal internal data request/sort foundation for future data components. The public Grid v3 APIs live under Components/Grid.
Installation
Reference the Razor Class Library from a Blazor app:
dotnet add <YourApp>.csproj reference src/MyBlazorUI/MyBlazorUI.csproj
Register services in Program.cs:
using MyBlazorUI.Extensions;
builder.Services.AddMyBlazorUI();
AddMyBlazorUI() registers:
MyJsModuleServiceMyToastService
Static Web Assets And Themes
Include one theme and the shared base stylesheet in your app document:
<link rel="stylesheet" href="_content/MyBlazorUI/themes/light.css" />
<link rel="stylesheet" href="_content/MyBlazorUI/myblazorui.css" />
Use the dark theme by swapping the theme file:
<link rel="stylesheet" href="_content/MyBlazorUI/themes/dark.css" />
JavaScript modules are loaded by components through MyJsModuleService:
_content/MyBlazorUI/js/popup.js_content/MyBlazorUI/js/dropdown.js
Usage Examples
<MyButton OnClick="SaveAsync">Save</MyButton>
<MyIconButton AriaLabel="Refresh">
<Icon><span>R</span></Icon>
</MyIconButton>
<MyTextBox @bind-Value="model.Name" Placeholder="Name" ShowClearButton="true" />
<MyCheckBox @bind-Value="model.IsActive" Label="Active" />
<MyPopup @bind-Visible="isPopupVisible" Title="Confirm" ShowFooter="true">
<ChildContent>
<p>Continue with this action?</p>
</ChildContent>
<FooterTemplate>
<MyButton Variant="MyButtonVariant.Secondary" OnClick="Cancel">Cancel</MyButton>
<MyButton OnClick="Confirm">Confirm</MyButton>
</FooterTemplate>
</MyPopup>
<MySelect Items="countries"
TextSelector="country => country.Name"
ValueSelector="country => country.Code"
@bind-Value="selectedCountryCode"
Placeholder="Choose a country" />
<MyToastProvider>
@Body
</MyToastProvider>
@code {
[Inject] private MyToastService Toasts { get; set; } = default!;
private void Notify() => Toasts.ShowSuccess("Profile saved.", "Saved");
}
<MyCard Title="Account" Subtitle="Profile details">
<p>Account content goes here.</p>
</MyCard>
<MyTabs @bind-ActiveIndex="activeTab">
<MyTabPage Title="General">General content</MyTabPage>
<MyTabPage Title="Security">Security content</MyTabPage>
</MyTabs>
<MyDropdown>
<TriggerTemplate>
<MyButton>Open menu</MyButton>
</TriggerTemplate>
<ChildContent>
<button type="button">Profile</button>
<button type="button">Settings</button>
</ChildContent>
</MyDropdown>
<MyFormLayout Columns="2">
<MyFormLayoutItem Label="First name">
<MyTextBox @bind-Value="model.FirstName" />
</MyFormLayoutItem>
<MyFormLayoutItem Label="Last name">
<MyTextBox @bind-Value="model.LastName" />
</MyFormLayoutItem>
</MyFormLayout>
<MyGrid TItem="UserDto" Items="users" PageSize="10">
<Columns>
<MyGridColumn TItem="UserDto" Title="Name" Field="user => user.Name" />
<MyGridColumn TItem="UserDto" Title="Email" Field="user => user.Email" />
<MyGridColumn TItem="UserDto" Title="Role" Field="user => user.Role" />
</Columns>
</MyGrid>
<MyGrid TItem="UserDto" Items="users">
<Columns>
<MyGridColumn TItem="UserDto" Title="Name" Field="user => user.Name">
<CellTemplate Context="user">
<strong>@user.Name</strong>
</CellTemplate>
</MyGridColumn>
<MyGridColumn TItem="UserDto" Title="Actions" Sortable="false" Filterable="false">
<CellTemplate Context="user">
<span @onclick:stopPropagation="true">
<MyButton Size="MyButtonSize.Small">Edit</MyButton>
</span>
</CellTemplate>
</MyGridColumn>
</Columns>
</MyGrid>
<MyGrid TItem="UserDto"
Items="users"
SelectionMode="MyGridSelectionMode.Multiple"
ShowSelectionColumn="true"
@bind-SelectedItems="selectedUsers"
KeySelector="user => user.Id">
<Columns>
<MyGridColumn TItem="UserDto" Title="Name" Field="user => user.Name" />
<MyGridColumn TItem="UserDto" Title="Email" Field="user => user.Email" />
<MyGridColumn TItem="UserDto" Title="Role" Field="user => user.Role" />
</Columns>
</MyGrid>
<MyGrid TItem="UserDto"
Items="users"
FilterMode="MyGridFilterMode.FilterRow"
FiltersChanged="HandleFiltersChanged">
<Columns>
<MyGridColumn TItem="UserDto" Title="Name" Field="user => user.Name" />
<MyGridColumn TItem="UserDto" Title="Role" Field="user => user.Role" />
<MyGridColumn TItem="UserDto" Title="Actions" Sortable="false" Filterable="false">
<CellTemplate Context="user">
<span @onclick:stopPropagation="true">
<MyButton Size="MyButtonSize.Small" Variant="MyButtonVariant.Secondary">Edit</MyButton>
<MyButton Size="MyButtonSize.Small" Variant="MyButtonVariant.Danger">Delete</MyButton>
</span>
</CellTemplate>
</MyGridColumn>
</Columns>
</MyGrid>
<label>
<input type="checkbox" @bind="showEmailColumn" />
Show Email
</label>
<MyGrid TItem="UserDto"
Items="users"
Density="MyGridDensity.Compact"
PageSize="10"
PageSizeOptions="new[] { 10, 20, 50 }">
<Columns>
<MyGridColumn TItem="UserDto" Title="Name" Field="user => user.Name" Width="220px" MinWidth="160px" />
<MyGridColumn TItem="UserDto" Title="Email" Field="user => user.Email" Visible="showEmailColumn" MinWidth="240px" />
<MyGridColumn TItem="UserDto" Title="Role" Field="user => user.Role" Width="140px" />
</Columns>
</MyGrid>
<MyGrid @ref="grid"
TItem="UserDto"
Items="users"
FilterMode="MyGridFilterMode.FilterRow">
<Columns>
<MyGridColumn TItem="UserDto" Title="Name" Field="user => user.Name" />
<MyGridColumn TItem="UserDto" Title="Email" Field="user => user.Email" />
</Columns>
</MyGrid>
@code {
private MyGrid<UserDto>? grid;
private MyGridPersistentState? capturedState;
private void CaptureState() => capturedState = grid?.GetPersistentState();
private async Task ApplyStateAsync()
{
if (grid is not null && capturedState is not null)
{
await grid.ApplyPersistentStateAsync(capturedState);
}
}
}
<MyGrid TItem="UserDto"
DataProvider="LoadUsersAsync"
PageSize="20">
<Columns>
<MyGridColumn TItem="UserDto" Title="Name" Field="user => user.Name" />
<MyGridColumn TItem="UserDto" Title="Email" Field="user => user.Email" />
</Columns>
</MyGrid>
@code {
private Task<MyGridDataResult<UserDto>> LoadUsersAsync(MyGridDataRequest request)
{
var query = users.AsEnumerable();
foreach (var filter in request.Filters)
{
if (filter.FieldName == nameof(UserDto.Name) && !string.IsNullOrWhiteSpace(filter.Value))
{
query = query.Where(user => user.Name.Contains(filter.Value, StringComparison.OrdinalIgnoreCase));
}
}
if (request.SortField == nameof(UserDto.Name))
{
query = request.SortDirection == MyGridSortDirection.Descending
? query.OrderByDescending(user => user.Name)
: query.OrderBy(user => user.Name);
}
var totalCount = query.Count();
return Task.FromResult(new MyGridDataResult<UserDto>
{
Items = query.Skip(request.PageIndex * request.PageSize).Take(request.PageSize).ToArray(),
TotalCount = totalCount
});
}
}
MyGrid supports client-side filtering, sorting, paging, single selection, multiple selection, column visibility, column sizing, density classes, page size selection, and persistent state snapshots when Items is used. When DataProvider is supplied, it takes precedence and receives MyGridDataRequest values for page index, page size, sort field, sort direction, and active text filters.
Commands
Build the solution:
dotnet build
Run tests:
dotnet test
Run the docs app:
dotnet run --project src/MyBlazorUI.Docs
Run the playground app:
dotnet run --project src/MyBlazorUI.Playground
Pack the Razor Class Library:
dotnet pack src/MyBlazorUI/MyBlazorUI.csproj -c Release
Packaging Notes
The RCL project includes NuGet metadata, XML documentation generation, README packaging, and static web assets under wwwroot. The package is prepared for local packing, but it is not published by this repository.
Known Limitations
- Grid v3 is intentionally lightweight.
- Grid localStorage persistence, full column chooser UI, drag column resizing, column reordering, advanced filter menus, date/number filters, editing, grouping, virtualization, export, frozen columns, master-detail, and advanced keyboard navigation are not implemented yet.
- Scheduler, Chart, Upload, RichTextEditor, and TreeView are not implemented.
MySelectcurrently uses native<select>behavior rather than a custom virtualized listbox.MyDropdownuses simple trigger-relative positioning.- Toast behavior is scoped to basic service-driven notifications.
Roadmap
Near-term hardening can continue with accessibility audits, visual regression coverage, package signing/publishing workflow, and richer examples. Future grid work can add localStorage state persistence, a column chooser component, drag resizing, column reordering, advanced filter operators per column, date and number filters, filter menus, editing, grouping, virtualization, export, frozen columns, and richer keyboard interaction without changing the basic Grid v3 API shape.
| Product | Versions 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. |
-
net10.0
- Microsoft.AspNetCore.Components.Web (>= 10.0.7)
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 |
|---|
Phase 7 release. Adds and hardens the MyGrid component with sorting, paging, templates, loading and empty states, selection, filtering, row actions, density modes, column width and visibility support, page size selector, pager summary, persistent grid state foundation, documentation, playground examples, and bUnit coverage.