Skip to content
Blazor logo

Blazor

Blazor component patterns for interactive web UIs.

ngxtm/devkit0installs8starsSecurity

SKILL.md

Full skill instructions

Blazor

Priority: P1 (OPERATIONAL)

Blazor component patterns for interactive web UIs.

Implementation Guidelines

  • Components: Keep components focused. Split large components into smaller ones.
  • Parameters: Use [Parameter] for one-way, EventCallback for two-way binding.
  • Cascading Values: Use for app-wide state (theme, user context).
  • State Management: Component state for local, Fluxor/​custom service for global.
  • Forms: EditForm with DataAnnotationsValidator or FluentValidation.
  • JS Interop: Use IJSRuntime sparingly. Prefer Blazor bindings.
  • Render Modes: Choose based on requirements (Server, WASM, Auto in .NET 8+).
  • Streaming: Use [StreamRendering] for long-loading components.

Anti-Patterns

  • No direct DOM manipulation: Use Blazor bindings and refs.
  • No StateHasChanged() in lifecycle: Called automatically after lifecycle methods.
  • No heavy computation in render: Move to OnInitialized or background task.
  • No sync JS interop in Server mode: Causes UI blocking.

Code

@* UserList.razor *@
@page "/​users"
@attribute [StreamRendering]
@inject IUserService UserService

<PageTitle>Users</​PageTitle>

<h3>Users</​h3>

@if (_users is null)
{
    <p><em>Loading...</​em></​p>
}
else if (_users.Count == 0)
{
    <p>No users found.</​p>
}
else
{
    <div class="user-grid">
        @foreach (var user in _users)
        {
            <UserCard User="user" OnDelete="HandleDelete" />
        }
    </​div>
}

@code {
    private List<User>? _users;

    protected override async Task OnInitializedAsync()
    {
        _users = await UserService.GetAllAsync();
    }

    private async Task HandleDelete(int userId)
    {
        await UserService.DeleteAsync(userId);
        _users = await UserService.GetAllAsync();
    }
}
@* UserCard.razor - Reusable component *@
<div class="card">
    <h5>@User.Name</​h5>
    <p>@User.Email</​p>
    <button @onclick="() => OnDelete.InvokeAsync(User.Id)">Delete</​button>
</​div>

@code {
    [Parameter, EditorRequired]
    public User User { get; set; } = default!;

    [Parameter]
    public EventCallback<int> OnDelete { get; set; }
}
@* EditForm with validation *@
<EditForm Model="@_model" OnValidSubmit="HandleSubmit" FormName="CreateUser">
    <DataAnnotationsValidator />
    <ValidationSummary class="text-danger" />

    <div class="mb-3">
        <label class="form-label">Name</​label>
        <InputText @bind-Value="_model.Name" class="form-control" />
        <ValidationMessage For="@(() => _model.Name)" />
    </​div>

    <div class="mb-3">
        <label class="form-label">Email</​label>
        <InputText @bind-Value="_model.Email" class="form-control" type="email" />
        <ValidationMessage For="@(() => _model.Email)" />
    </​div>

    <button type="submit" class="btn btn-primary" disabled="@_isSubmitting">
        @if (_isSubmitting)
        {
            <span class="spinner-border spinner-border-sm"></​span>
        }
        Submit
    </​button>
</​EditForm>

@code {
    private CreateUserModel _model = new();
    private bool _isSubmitting;

    private async Task HandleSubmit()
    {
        _isSubmitting = true;
        await UserService.CreateAsync(_model);
        _isSubmitting = false;
        NavigationManager.NavigateTo("/​users");
    }
}

Reference & Examples

For lifecycle, state management, and JS interop: See references/​REFERENCE.md.

Related Topics

aspnet-core | razor-pages | security