SCABlazor 1.0.0
dotnet add package SCABlazor --version 1.0.0
NuGet\Install-Package SCABlazor -Version 1.0.0
<PackageReference Include="SCABlazor" Version="1.0.0" />
<PackageVersion Include="SCABlazor" Version="1.0.0" />
<PackageReference Include="SCABlazor" />
paket add SCABlazor --version 1.0.0
#r "nuget: SCABlazor, 1.0.0"
#:package SCABlazor@1.0.0
#addin nuget:?package=SCABlazor&version=1.0.0
#tool nuget:?package=SCABlazor&version=1.0.0
SCABlazor
SCABlazor 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:
SCAButtonSCAIconButtonSCATextBoxSCACheckBoxSCAPopup
Phase 2:
SCASelectSCAToastService,SCAToastProvider,SCAToastContainerSCACardSCATabs,SCATabPageSCADropdownSCAFormLayout,SCAFormLayoutItem
Phase 4, Phase 5, and Phase 7:
SCAGrid<TItem>SCAGridColumn<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/SCABlazor/. - Browser behavior uses component-focused JS modules, not global scripts.
- Inputs integrate with
EditFormthroughInputBase<TValue>. - Public services are registered through
builder.Services.AddSCABlazor().
Solution Structure
src/
SCABlazor/
Components/
Button/
IconButton/
TextBox/
CheckBox/
Popup/
Select/
Toast/
Card/
Tabs/
Dropdown/
FormLayout/
Grid/
Core/
Data/
Services/
Interop/
Themes/
Utilities/
Extensions/
wwwroot/
themes/
js/
SCABlazor.Docs/
SCABlazor.Playground/
tests/
SCABlazor.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/SCABlazor/SCABlazor.csproj
Register services in Program.cs:
using SCABlazor.Extensions;
builder.Services.AddSCABlazor();
AddSCABlazor() registers:
SCAJsModuleServiceSCAToastService
Static Web Assets And Themes
Include one theme and the shared base stylesheet in your app document:
<link rel="stylesheet" href="_content/SCABlazor/themes/light.css" />
<link rel="stylesheet" href="_content/SCABlazor/scablazor.css" />
Use the dark theme by swapping the theme file:
<link rel="stylesheet" href="_content/SCABlazor/themes/dark.css" />
JavaScript modules are loaded by components through SCAJsModuleService:
_content/SCABlazor/js/popup.js_content/SCABlazor/js/dropdown.js
Usage Examples
<SCAButton OnClick="SaveAsync">Save</SCAButton>
<SCAIconButton AriaLabel="Refresh">
<Icon><span>R</span></Icon>
</SCAIconButton>
<SCATextBox @bind-Value="model.Name" Placeholder="Name" ShowClearButton="true" />
<SCACheckBox @bind-Value="model.IsActive" Label="Active" />
<SCAPopup @bind-Visible="isPopupVisible" Title="Confirm" ShowFooter="true">
<ChildContent>
<p>Continue with this action?</p>
</ChildContent>
<FooterTemplate>
<SCAButton Variant="SCAButtonVariant.Secondary" OnClick="Cancel">Cancel</SCAButton>
<SCAButton OnClick="Confirm">Confirm</SCAButton>
</FooterTemplate>
</SCAPopup>
<SCASelect Items="countries"
TextSelector="country => country.Name"
ValueSelector="country => country.Code"
@bind-Value="selectedCountryCode"
Placeholder="Choose a country" />
<SCAToastProvider>
@Body
</SCAToastProvider>
@code {
[Inject] private SCAToastService Toasts { get; set; } = default!;
private void Notify() => Toasts.ShowSuccess("Profile saved.", "Saved");
}
<SCACard Title="Account" Subtitle="Profile details">
<p>Account content goes here.</p>
</SCACard>
<SCATabs @bind-ActiveIndex="activeTab">
<SCATabPage Title="General">General content</SCATabPage>
<SCATabPage Title="Security">Security content</SCATabPage>
</SCATabs>
<SCADropdown>
<TriggerTemplate>
<SCAButton>Open menu</SCAButton>
</TriggerTemplate>
<ChildContent>
<button type="button">Profile</button>
<button type="button">Settings</button>
</ChildContent>
</SCADropdown>
<SCAFormLayout Columns="2">
<SCAFormLayoutItem Label="First name">
<SCATextBox @bind-Value="model.FirstName" />
</SCAFormLayoutItem>
<SCAFormLayoutItem Label="Last name">
<SCATextBox @bind-Value="model.LastName" />
</SCAFormLayoutItem>
</SCAFormLayout>
<SCAGrid TItem="UserDto" Items="users" PageSize="10">
<Columns>
<SCAGridColumn TItem="UserDto" Title="Name" Field="user => user.Name" />
<SCAGridColumn TItem="UserDto" Title="Email" Field="user => user.Email" />
<SCAGridColumn TItem="UserDto" Title="Role" Field="user => user.Role" />
</Columns>
</SCAGrid>
<SCAGrid TItem="UserDto" Items="users">
<Columns>
<SCAGridColumn TItem="UserDto" Title="Name" Field="user => user.Name">
<CellTemplate Context="user">
<strong>@user.Name</strong>
</CellTemplate>
</SCAGridColumn>
<SCAGridColumn TItem="UserDto" Title="Actions" Sortable="false" Filterable="false">
<CellTemplate Context="user">
<span @onclick:stopPropagation="true">
<SCAButton Size="SCAButtonSize.Small">Edit</SCAButton>
</span>
</CellTemplate>
</SCAGridColumn>
</Columns>
</SCAGrid>
<SCAGrid TItem="UserDto"
Items="users"
SelectionMode="SCAGridSelectionMode.Multiple"
ShowSelectionColumn="true"
@bind-SelectedItems="selectedUsers"
KeySelector="user => user.Id">
<Columns>
<SCAGridColumn TItem="UserDto" Title="Name" Field="user => user.Name" />
<SCAGridColumn TItem="UserDto" Title="Email" Field="user => user.Email" />
<SCAGridColumn TItem="UserDto" Title="Role" Field="user => user.Role" />
</Columns>
</SCAGrid>
<SCAGrid TItem="UserDto"
Items="users"
FilterMode="SCAGridFilterMode.FilterRow"
FiltersChanged="HandleFiltersChanged">
<Columns>
<SCAGridColumn TItem="UserDto" Title="Name" Field="user => user.Name" />
<SCAGridColumn TItem="UserDto" Title="Role" Field="user => user.Role" />
<SCAGridColumn TItem="UserDto" Title="Actions" Sortable="false" Filterable="false">
<CellTemplate Context="user">
<span @onclick:stopPropagation="true">
<SCAButton Size="SCAButtonSize.Small" Variant="SCAButtonVariant.Secondary">Edit</SCAButton>
<SCAButton Size="SCAButtonSize.Small" Variant="SCAButtonVariant.Danger">Delete</SCAButton>
</span>
</CellTemplate>
</SCAGridColumn>
</Columns>
</SCAGrid>
<label>
<input type="checkbox" @bind="showEmailColumn" />
Show Email
</label>
<SCAGrid TItem="UserDto"
Items="users"
Density="SCAGridDensity.Compact"
PageSize="10"
PageSizeOptions="new[] { 10, 20, 50 }">
<Columns>
<SCAGridColumn TItem="UserDto" Title="Name" Field="user => user.Name" Width="220px" MinWidth="160px" />
<SCAGridColumn TItem="UserDto" Title="Email" Field="user => user.Email" Visible="showEmailColumn" MinWidth="240px" />
<SCAGridColumn TItem="UserDto" Title="Role" Field="user => user.Role" Width="140px" />
</Columns>
</SCAGrid>
<SCAGrid @ref="grid"
TItem="UserDto"
Items="users"
FilterMode="SCAGridFilterMode.FilterRow">
<Columns>
<SCAGridColumn TItem="UserDto" Title="Name" Field="user => user.Name" />
<SCAGridColumn TItem="UserDto" Title="Email" Field="user => user.Email" />
</Columns>
</SCAGrid>
@code {
private SCAGrid<UserDto>? grid;
private SCAGridPersistentState? capturedState;
private void CaptureState() => capturedState = grid?.GetPersistentState();
private async Task ApplyStateAsync()
{
if (grid is not null && capturedState is not null)
{
await grid.ApplyPersistentStateAsync(capturedState);
}
}
}
<SCAGrid TItem="UserDto"
DataProvider="LoadUsersAsync"
PageSize="20">
<Columns>
<SCAGridColumn TItem="UserDto" Title="Name" Field="user => user.Name" />
<SCAGridColumn TItem="UserDto" Title="Email" Field="user => user.Email" />
</Columns>
</SCAGrid>
@code {
private Task<SCAGridDataResult<UserDto>> LoadUsersAsync(SCAGridDataRequest 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 == SCAGridSortDirection.Descending
? query.OrderByDescending(user => user.Name)
: query.OrderBy(user => user.Name);
}
var totalCount = query.Count();
return Task.FromResult(new SCAGridDataResult<UserDto>
{
Items = query.Skip(request.PageIndex * request.PageSize).Take(request.PageSize).ToArray(),
TotalCount = totalCount
});
}
}
SCAGrid 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 SCAGridDataRequest 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/SCABlazor.Docs
Run the playground app:
dotnet run --project src/SCABlazor.Playground
Pack the Razor Class Library:
dotnet pack src/SCABlazor/SCABlazor.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.
SCASelectcurrently uses native<select>behavior rather than a custom virtualized listbox.SCADropdownuses 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.
Initial SCABlazor package release based on the MyBlazorUI 0.3.1 codebase. Includes themed Blazor components, popup, toast, tabs, dropdown, form layout, and a lightweight grid with sorting, paging, selection, filtering, density modes, column visibility, column width support, page size selector, pager summary, and persistent grid state foundation.