internal/skills/content/blazor/SKILL.md
Blazor framework guardrails, patterns, and best practices for AI-assisted development. Use when working with Blazor projects, or when the user mentions Blazor. Provides WebAssembly/Server patterns, component lifecycle, SignalR, and state management guidelines.
npx skillsauth add ar4mirez/samuel blazorInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Applies to: Blazor (.NET 8+), C# 12+, WebAssembly, Server, Blazor United, Interactive Web Apps
.razor components that own their state and rendering| Model | Runs In | Best For | |-------|---------|----------| | WebAssembly | Browser (WASM) | Offline-capable apps, PWAs, reduced server load | | Server | Server (SignalR) | Low-latency, SEO, thin clients | | United (.NET 8) | Hybrid SSR + interactive | Per-component render mode selection |
[Parameter, EditorRequired] for required parameters[Parameter] properties directly -- use local state insteadEventCallback<T> for parent-child communication (not Action<T>)IDisposable when subscribing to events, timers, or state changes@key directive on list-rendered items for DOM diffing performance<ErrorBoundary> for graceful error handlingRenderFragment and RenderFragment<T> for templated componentsOnInitialized/OnInitializedAsync -- use OnAfterRenderOnParametersSet for reacting to parameter changes (not property setters)StateHasChanged() only when necessary (avoid triggering excessive re-renders)ShouldRender() override to prevent unnecessary renders on hot pathsCancellationToken with async lifecycle methods to handle disposal@bind-Value for two-way binding with input components@bind:event to control when binding updates (e.g., oninput vs onchange)EditForm with DataAnnotationsValidator for form validation@bind and @onchange on the same element (they conflict)[Authorize] attribute on pages requiring authentication<AuthorizeView> for conditional UI based on auth state<Virtualize> for large lists instead of @foreach@rendermode appropriately (static SSR by default, interactive only when needed)[StreamRendering] for pages with async data loadingIMemoryCache or Blazored.LocalStorage for client-side cachingLazyAssemblyLoaderMyApp/
├── MyApp.Client/ # Blazor WebAssembly / interactive
│ ├── Pages/ # Routable components (@page)
│ │ ├── Home.razor
│ │ └── Users/
│ │ ├── UserList.razor
│ │ ├── UserDetail.razor
│ │ └── UserForm.razor
│ ├── Components/ # Reusable components
│ │ ├── Layout/
│ │ │ ├── MainLayout.razor
│ │ │ └── NavMenu.razor
│ │ ├── Common/
│ │ │ ├── LoadingSpinner.razor
│ │ │ ├── ErrorBoundary.razor
│ │ │ └── Modal.razor
│ │ └── Users/
│ │ └── UserCard.razor
│ ├── Services/ # Client-side services
│ │ ├── IUserService.cs
│ │ └── UserService.cs
│ ├── State/ # State management
│ │ └── AppState.cs
│ ├── wwwroot/ # Static assets
│ ├── _Imports.razor # Global using directives
│ ├── App.razor # Root component
│ └── Program.cs # DI and startup
├── MyApp.Server/ # API / server host
│ ├── Controllers/
│ └── Program.cs
├── MyApp.Shared/ # Shared models and DTOs
│ ├── Models/
│ └── DTOs/
├── tests/
│ ├── MyApp.Client.Tests/ # bUnit component tests
│ └── MyApp.Server.Tests/ # API integration tests
└── MyApp.sln
| Layer | Purpose | Depends On | |-------|---------|------------| | Client | UI components, pages, client services | Shared | | Server | API endpoints, server logic, auth | Shared | | Shared | Models, DTOs, validation attributes | Nothing | | Tests | bUnit component tests, API tests | Client, Server |
@page "/users"
@attribute [Authorize]
@inject IUserService UserService
<PageTitle>Users</PageTitle>
@if (loading)
{
<LoadingSpinner />
}
else if (error is not null)
{
<div class="alert alert-danger">
<p>@error</p>
<button class="btn btn-outline-danger" @onclick="LoadUsers">Retry</button>
</div>
}
else if (users is null || users.Count == 0)
{
<div class="alert alert-info">No users found.</div>
}
else
{
@foreach (var user in users)
{
<UserCard @key="user.Id" User="user" OnEdit="() => Edit(user.Id)" />
}
}
@code {
private List<UserResponse>? users;
private bool loading = true;
private string? error;
protected override async Task OnInitializedAsync()
{
await LoadUsers();
}
private async Task LoadUsers()
{
loading = true;
error = null;
try
{
users = await UserService.GetUsersAsync();
}
catch (Exception ex)
{
error = ex.Message;
}
finally
{
loading = false;
}
}
private void Edit(long id) => Navigation.NavigateTo($"/users/{id}/edit");
}
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">@User.FirstName @User.LastName</h5>
<small class="text-muted">@User.Email</small>
<span class="badge @(User.Active ? "bg-success" : "bg-secondary")">
@(User.Active ? "Active" : "Inactive")
</span>
</div>
<div class="card-footer">
<button class="btn btn-sm btn-outline-primary" @onclick="OnEdit">Edit</button>
<button class="btn btn-sm btn-outline-danger" @onclick="OnDelete">Delete</button>
</div>
</div>
@code {
[Parameter, EditorRequired]
public UserResponse User { get; set; } = default!;
[Parameter]
public EventCallback OnEdit { get; set; }
[Parameter]
public EventCallback OnDelete { get; set; }
}
@code {
[Parameter] public string Title { get; set; } = "Modal";
[Parameter] public RenderFragment? BodyContent { get; set; }
[Parameter] public RenderFragment? FooterContent { get; set; }
[Parameter] public EventCallback OnClose { get; set; }
}
Use RenderFragment for content slots, RenderFragment<T> for typed template parameters.
<EditForm Model="model" OnValidSubmit="HandleSubmit">
<DataAnnotationsValidator />
<ValidationSummary class="alert alert-danger" />
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<InputText id="email" class="form-control" @bind-Value="model.Email" />
<ValidationMessage For="@(() => model.Email)" class="text-danger" />
</div>
<button type="submit" class="btn btn-primary" disabled="@submitting">
@(submitting ? "Saving..." : "Save")
</button>
</EditForm>
@page "/users/{Id:long}"
@page "/users/{Id:long}/edit"
@code {
[Parameter]
public long Id { get; set; }
}
@inject NavigationManager Navigation
Navigation.NavigateTo("/users"); // Client-side navigation
Navigation.NavigateTo("/users/1", forceLoad: true); // Full page reload
Use <NavLink> with Match="NavLinkMatch.Prefix" or NavLinkMatch.All for active CSS state.
public class AppState
{
private UserResponse? _currentUser;
public UserResponse? CurrentUser
{
get => _currentUser;
set { _currentUser = value; NotifyStateChanged(); }
}
public event Action? OnChange;
private void NotifyStateChanged() => OnChange?.Invoke();
}
@inject AppState State
@implements IDisposable
<span>@State.CurrentUser?.FirstName</span>
@code {
protected override void OnInitialized()
{
State.OnChange += StateHasChanged;
}
public void Dispose()
{
State.OnChange -= StateHasChanged;
}
}
Use <CascadingValue Value="@theme"> in layouts. Consume with [CascadingParameter] in children. Good for theme, auth state, and locale. Avoid for frequently changing data (triggers full subtree re-render).
@inject IJSRuntime JS
@code {
// Only call in OnAfterRenderAsync, not OnInitializedAsync
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var width = await JS.InvokeAsync<int>("blazorInterop.getWindowWidth");
}
}
}
Encapsulate JS interop behind service classes registered in DI. Use InvokeVoidAsync for calls without return values, InvokeAsync<T> for typed returns.
@* Static SSR (default -- no interactivity) *@
<StaticComponent />
@* Interactive Server (SignalR) *@
<InteractiveComponent @rendermode="InteractiveServer" />
@* Interactive WebAssembly *@
<InteractiveComponent @rendermode="InteractiveWebAssembly" />
@* Interactive Auto (starts Server, switches to WASM when downloaded) *@
<InteractiveComponent @rendermode="InteractiveAuto" />
Add @attribute [StreamRendering] to pages with async data loading. The page renders immediately with placeholder content, then streams updates as OnInitializedAsync completes. Ideal for SSR pages loading slow data.
using Bunit;
using FluentAssertions;
public class UserCardTests : TestContext
{
[Fact]
public void UserCard_DisplaysUserInfo()
{
var user = new UserResponse(1, "[email protected]", "John", "Doe",
"User", true, DateTime.UtcNow);
var cut = RenderComponent<UserCard>(p => p.Add(x => x.User, user));
cut.Find(".card-title").TextContent.Should().Contain("John Doe");
cut.Find("small.text-muted").TextContent.Should().Contain("[email protected]");
}
[Fact]
public void UserCard_EditButton_InvokesCallback()
{
var user = new UserResponse(1, "[email protected]", "J", "D", "User", true, DateTime.UtcNow);
var clicked = false;
var cut = RenderComponent<UserCard>(p => p
.Add(x => x.User, user)
.Add(x => x.OnEdit, EventCallback.Factory.Create(this, () => clicked = true)));
cut.Find("button.btn-outline-primary").Click();
clicked.Should().BeTrue();
}
}
dotnet new blazorwasm -o MyApp.Client # New Blazor WASM app
dotnet new blazorserver -o MyApp.Server # New Blazor Server app
dotnet new blazor -o MyApp # New Blazor Web App (.NET 8 United)
dotnet watch run --project MyApp.Client # Dev server with hot reload
dotnet build # Build
dotnet publish -c Release # Publish
dotnet test # Run tests
dotnet add package bunit # Add bUnit testing
| Package | Purpose |
|---------|---------|
| Microsoft.AspNetCore.Components.WebAssembly | WASM hosting |
| Microsoft.AspNetCore.Components.WebAssembly.Authentication | WASM auth |
| Blazored.LocalStorage | Browser local storage |
| Blazored.Toast | Toast notifications |
| bunit | Component testing |
| Fluxor.Blazor.Web | Redux-style state management |
@key on list items for efficient DOM diffingEventCallback over Action for component eventsIDisposable for event handler and timer cleanup<Virtualize> for rendering large collectionsHttpClient for API callsCascadingValue for widely shared state (theme, auth)@rendermode per-component in .NET 8 UnitedOnInitialized (use OnAfterRender)[Parameter] properties directlyFor detailed patterns and examples, see:
development
Zig language guardrails, patterns, and best practices for AI-assisted development. Use when working with Zig files (.zig), build.zig, or when the user mentions Zig. Provides comptime patterns, allocator conventions, C interop guidelines, and testing standards specific to this project's coding standards.
tools
WordPress framework guardrails, patterns, and best practices for AI-assisted development. Use when working with WordPress projects, or when the user mentions WordPress. Provides theme development, plugin architecture, REST API, blocks, and security guidelines.
tools
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs. Use when testing web apps, automating browser interactions, or debugging frontend issues.
tools
Suite of tools for creating elaborate, multi-component web applications using modern frontend technologies (React, Tailwind CSS, shadcn/ui). Use for complex projects requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX pages.