Table of Contents

Blazor components

In the below table, you can see all shared Blazor components (.razor) that ship in the CodeBlock.DevKit.Web.Blazor.Server package. They compile to CodeBlock.DevKit.Web.Blazor.Server.Components.<ComponentName>.

Name What it is for
Alert Inline status or message area.
ComponentLoading Loading state while async work runs.
ConfirmDialog Modal body with confirm/cancel (Blazored Modal).
ConnectionState Blazor disconnect, reconnect, and fatal error overlays.
CustomAuthorizeRouteView Route view with authorization and optional in-progress UI.
HeadTags Document <head> tags (title, meta) from Blazor.
InformationDialog Read-only information modal content.
Pagination Pagination controls.
Redirect Navigates to a target URL.
RedirectToLogin Sends unauthenticated users to login.
SelectLanguage Language / culture selector.
SimpleLayout Minimal Blazor chrome for child pages and components.
SplashScreen Initial splash and loading overlay.
_Imports Shared @using directives for the shared component library.

Reference

In the below sections, you can see all information about each UI component so you know where it lives and what it is used for. You can also copy the embedded source to create your own implementation when you customize those components.

Alert.razor

Shows an inline notice or status (success, warning, error) without opening a modal—use for feedback next to a form or section.

  • Path: CodeBlock.DevKit/src/1-Libraries/Source/Web.Blazor.Server/Components/Alert.razor
  • Namespace / type: CodeBlock.DevKit.Web.Blazor.Server.ComponentsAlert
@using CodeBlock.DevKit.Application.Services
@using CodeBlock.DevKit.Core.Resources
@using CodeBlock.DevKit.Web.Blazor.Server.Helpers
@inject ICurrentUser CurrentUser
@inject IStringLocalizer<CoreResource> CoreLocalizer


<PageTitle>@Title</PageTitle>

<div class="simple-card-header">
    <h3 class="simple-card-title text-@GetAlertColorClass()">@Title</h3>
</div>

<div class="simple-body">

    <div>
        <div class="simple-status-icon-container">
            <i class="@GetIconClass() simple-status-icon @GetAlertColorClass()"></i>
        </div>
        <div class="simple-alert-content text-@GetAlertColorClass()">
            <p>@Message</p>
        </div>
    </div>
</div>

@if (ShowLinkedButtons)
{
    <div class="simple-footer">
        <div class="simple-btn-grid">
            <a href="/" class="simple-btn simple-btn-primary">
                <i class="bi bi-house me-2"></i>@CoreLocalizer[CoreResource.Home]
            </a>
            @if (!CurrentUser.IsAuthenticated())
            {
                <a href="/login" class="simple-btn simple-btn-outline">
                    <i class="bi bi-box-arrow-in-right me-2"></i>@CoreLocalizer[CoreResource.Login]
                </a>
            }
            else
            {
                <a href="/dashboard" class="simple-btn simple-btn-outline">
                    <i class="bi bi-person-workspace me-2"></i>@CoreLocalizer[CoreResource.Dashboard]
                </a>
            }

        </div>
    </div>
}


@code {
    [Parameter]
    public AlertType Type { get; set; } = AlertType.Warning;

    [Parameter]
    public string Title { get; set; }

    [Parameter]
    public string Message { get; set; }

    [Parameter]
    public bool ShowLinkedButtons { get; set; } = true;

    protected virtual string GetIconClass()
    {
        switch (Type)
        {
            case AlertType.Warning:
                return "bi bi-exclamation-triangle";
            case AlertType.Error:
                return "bi bi-x-circle";
            case AlertType.Success:
                return "bi bi-check-circle";
            case AlertType.Info:
                return "bi bi-info-circle";
            default:
                return "bi bi-exclamation-triangle";
        }
    }

    protected virtual string GetAlertColorClass()
    {
        switch (Type)
        {
            case AlertType.Warning:
                return "warning";
            case AlertType.Error:
                return "danger";
            case AlertType.Success:
                return "success";
            case AlertType.Info:
                return "info";
            default:
                return "warning";
        }
    }

}

ComponentLoading.razor

Wraps content that depends on async data so users see a loading placeholder instead of an empty or broken layout.

  • Path: CodeBlock.DevKit/src/1-Libraries/Source/Web.Blazor.Server/Components/ComponentLoading.razor
  • Namespace / type: CodeBlock.DevKit.Web.Blazor.Server.ComponentsComponentLoading

<div class="row mb-1 position-relative">
    <div class="d-flex justify-content-center align-items-center w-100 height-md-70 height-xs-55">
        <div class="spinner-border @CssClass" role="status">
            <span class="visually-hidden">Loading...</span>
        </div>
    </div>
</div>

@code {
    [Parameter]
    public string CssClass { get; set; } = "text-primary";
}

ConfirmDialog.razor

Modal body for “are you sure?” flows; pairs with Blazored Modal for confirm/cancel actions.

  • Path: CodeBlock.DevKit/src/1-Libraries/Source/Web.Blazor.Server/Components/ConfirmDialog.razor
  • Namespace / type: CodeBlock.DevKit.Web.Blazor.Server.ComponentsConfirmDialog
@using Blazored.Modal
@using Blazored.Modal.Services
@using CodeBlock.DevKit.Core.Resources
<div>
    <div class="alert alert-warning">
        @Message 
        <hr />
        <strong>@Localizer[CoreResource.Confirm_Dialog_Message]</strong>
    </div>

    <button @onclick="Confirm" class="btn f-sm btn-danger">@Localizer[CoreResource.Confirm_Dialog_Confirm_Btn]</button>
    <button @onclick="Cancel" class="btn f-sm btn-warning">@Localizer[CoreResource.Confirm_Dialog_Cancel_Btn]</button>
</div>

@code {

    [CascadingParameter]
    BlazoredModalInstance ModalInstance { get; set; }

    [Parameter]
    public string Message { get; set; }


    async Task Confirm() => await ModalInstance.CloseAsync(ModalResult.Ok(true));
    async Task Cancel() => await ModalInstance.CancelAsync();

}

ConnectionState.razor

Handles SignalR circuit disconnect, reconnect, and hard failures so users see reconnect prompts instead of a blank screen.

  • Path: CodeBlock.DevKit/src/1-Libraries/Source/Web.Blazor.Server/Components/ConnectionState.razor
  • Namespace / type: CodeBlock.DevKit.Web.Blazor.Server.ComponentsConnectionState
@using CodeBlock.DevKit.Core.Resources
<div id="blazor-error-ui" class="reconnect-modal" style="display:none;">
    <div class="cb-flex cb-flex-column cb-min-height-100 cb-justify-center cb-align-center">
        <div class="cb-card cb-bg-dark cb-shadow cb-min-height-30 cb-width-responsive">
            <div class="cb-card-header cb-text-warning cb-text-center cb-heading-2 cb-padding-3">@Localizer[CoreResource.Connection_State_Error_Title]</div>
            <div class="cb-card-body cb-bg-dark cb-padding-3 cb-text-center cb-text-light">
                <p>
                    @Localizer[CoreResource.Connection_State_Error_Content]
                </p>
                <i class="bi bi-exclamation-triangle-fill cb-opacity-30" style="font-size:8rem;"></i>
            </div>
            <div class="cb-card-footer cb-bg-dark">
                <a onclick="location.reload()" class="cb-btn cb-font-sm cb-btn-warning cb-block">@Localizer[CoreResource.Connection_State_Reload_Btn]</a>
            </div>
        </div>
    </div>
</div>

<div id="components-reconnect-modal" class="reconnect-modal components-reconnect-hide">

    <div id="components-reconnect-modal-show" class="show" style="display:none;">
        <div class="cb-flex cb-flex-column cb-min-height-100 cb-justify-center cb-align-center">
            <div class="cb-card cb-bg-dark cb-shadow cb-min-height-30 cb-width-responsive">
                <div class="cb-card-header cb-text-warning cb-text-center cb-heading-2 cb-padding-3">@Localizer[CoreResource.Connection_State_Disconnect_Title]</div>
                <div class="cb-card-body cb-bg-dark cb-padding-3 cb-text-center cb-text-light">
                    <p>
                        @Localizer[CoreResource.Connection_State_Disconnect_Content]
                    </p>
                    <i class="bi bi-exclamation-triangle-fill cb-opacity-30" style="font-size:8rem;"></i>
                </div>
            </div>
        </div>
    </div>

    <div id="components-reconnect-modal-failed" class="failed" style="display:none;">
        <div class="cb-flex cb-flex-column cb-min-height-100 cb-justify-center cb-align-center">
            <div class="cb-card cb-bg-dark cb-shadow cb-min-height-30 cb-width-responsive">
                <div class="cb-card-header cb-text-warning cb-text-center cb-heading-2 cb-padding-3">@Localizer[CoreResource.Connection_State_Reconnect_Title]</div>
                <div class="cb-card-body cb-bg-dark cb-padding-3 cb-text-center cb-text-light">
                    <p>
                        @Localizer[CoreResource.Connection_State_Reject_Content]
                    </p>
                    <i class="bi bi-exclamation-triangle-fill cb-opacity-30" style="font-size:8rem;"></i>
                </div>
                <div class="cb-card-footer cb-bg-dark">
                    <a onclick="window.Blazor.reconnect()" class="cb-btn cb-font-sm cb-btn-warning cb-block">@Localizer[CoreResource.Connection_State_Retry_Btn]</a>
                </div>
            </div>
        </div>
    </div>

    <div id="components-reconnect-modal-rejected" class="rejected" style="display:none;">
        <div class="cb-flex cb-flex-column cb-min-height-100 cb-justify-center cb-align-center">
            <div class="cb-card cb-bg-dark cb-shadow cb-min-height-30 cb-width-responsive">
                <div class="cb-card-header cb-text-warning cb-text-center cb-heading-2 cb-padding-3">@Localizer[CoreResource.Connection_State_Reject_Title]</div>
                <div class="cb-card-body cb-bg-dark cb-padding-3 cb-text-center cb-text-light">
                    <p>
                        @Localizer[CoreResource.Connection_State_Reject_Content]
                    </p>
                    <i class="bi bi-exclamation-triangle-fill cb-opacity-30" style="font-size:8rem;"></i>
                </div>
                <div class="cb-card-footer cb-bg-dark">
                    <a onclick="location.reload()" class="cb-btn cb-font-sm cb-btn-warning cb-block">@Localizer[CoreResource.Connection_State_Retry_Btn]</a>
                </div>
            </div>
        </div>
    </div>
</div>

CustomAuthorizeRouteView.razor

Authorized routing: shows NotAuthorized content, optional in-progress UI, and wires AuthorizeRouteView behavior for the app shell.

  • Path: CodeBlock.DevKit/src/1-Libraries/Source/Web.Blazor.Server/Components/CustomAuthorizeRouteView.razor
  • Namespace / type: CodeBlock.DevKit.Web.Blazor.Server.ComponentsCustomAuthorizeRouteView
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Rendering
@using CodeBlock.DevKit.Web.Blazor.Server.Services
@using CodeBlock.DevKit.Web.Blazor.Server.Components
@using System.Reflection
@using System.Linq
@using System.Security.Claims
@inject ICustomComponentActivator ComponentActivator
@inject IAuthorizationService AuthorizationService

@if (IsAuthorizing)
{
    @if (Authorizing != null)
    {
        @Authorizing
    }
    else
    {
        <div>Authorizing...</div>
    }
}
else if (Authorized)
{
    @if (EffectiveLayoutType != null)
    {
        <CascadingValue Value="@this">
            <DynamicComponent Type="@EffectiveLayoutType" Parameters="@(GetLayoutParameters())" />
        </CascadingValue>
    }
    else
    {
        <DynamicComponent Type="@RouteData.PageType" Parameters="@(GetRouteParameters())" />
    }
}
else
{
    @if (NotAuthorized != null)
    {
        @NotAuthorized(_currentAuthenticationState)
    }
    else
    {
        <div>Not authorized</div>
    }
}

@code {
    [CascadingParameter]
    private Task<AuthenticationState> AuthenticationState { get; set; }

    [Parameter]
    public RouteData RouteData { get; set; } = default!;

    [Parameter]
    public Type DefaultLayout { get; set; }

    [Parameter]
    public RenderFragment<AuthenticationState> NotAuthorized { get; set; }

    [Parameter]
    public RenderFragment Authorizing { get; set; }

    protected bool Authorized { get; set; }
    protected bool IsAuthorizing { get; set; } = true;

    private Type? EffectiveLayoutType => GetEffectiveLayoutType();
    private AuthenticationState _currentAuthenticationState = new(new ClaimsPrincipal());

    protected override async Task OnParametersSetAsync()
    {
        if (AuthenticationState != null)
        {
            var authState = await AuthenticationState;
            _currentAuthenticationState = authState;
            var authorized = await IsAuthorizedAsync(authState);
            Authorized = authorized;
            IsAuthorizing = false;
        }
    }

    private async Task<bool> IsAuthorizedAsync(AuthenticationState authenticationState)
    {
        var pageType = RouteData.PageType;
        var authorizeAttributes = pageType.GetCustomAttributes<AuthorizeAttribute>();

        if (!authorizeAttributes.Any())
        {
            return true; // No authorization required
        }

        var user = authenticationState.User;

        foreach (var authorizeAttribute in authorizeAttributes)
        {
            if (!string.IsNullOrEmpty(authorizeAttribute.Policy))
            {
                // Policy-based authorization
                var policyResult = await AuthorizationService.AuthorizeAsync(user, authorizeAttribute.Policy);
                if (!policyResult.Succeeded)
                {
                    return false;
                }
            }
            else if (!string.IsNullOrEmpty(authorizeAttribute.Roles))
            {
                // Role-based authorization
                var roles = authorizeAttribute.Roles.Split(',').Select(role => role.Trim());
                if (!roles.Any(user.IsInRole))
                {
                    return false;
                }
            }
            else
            {
                // Just requires authentication
                if (!user.Identity?.IsAuthenticated ?? true)
                {
                    return false;
                }
            }
        }

        return true;
    }

    private Type? GetEffectiveLayoutType()
    {
        // Get the layout from the component (handles overrides automatically)
        var layoutType = ComponentActivator.GetLayoutType(RouteData.PageType);
        
        // Return the component's layout or fall back to default
        return layoutType ?? DefaultLayout;
    }

    private Dictionary<string, object> GetLayoutParameters()
    {
        return new Dictionary<string, object>
        {
            [nameof(LayoutComponentBase.Body)] = GetPageContent()
        };
    }

    private RenderFragment GetPageContent()
    {
        return builder =>
        {
            builder.OpenComponent(0, RouteData.PageType);
            foreach (var kvp in RouteData.RouteValues)
            {
                builder.AddAttribute(1, kvp.Key, kvp.Value);
            }
            builder.CloseComponent();
        };
    }

    private Dictionary<string, object> GetRouteParameters()
    {
        return RouteData.RouteValues.ToDictionary(
            kvp => kvp.Key, 
            kvp => kvp.Value ?? new object()
        );
    }
} 

HeadTags.razor

Updates the HTML <head> (title, meta) from Blazor so each route can set document metadata without a full page round-trip.

  • Path: CodeBlock.DevKit/src/1-Libraries/Source/Web.Blazor.Server/Components/HeadTags.razor
  • Namespace / type: CodeBlock.DevKit.Web.Blazor.Server.ComponentsHeadTags
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="msapplication-TileColor" content="#3B32FF">
<meta name="theme-color" content="#3B32FF">
<link rel="icon" href="/favicon.ico" type="image/x-icon">
<link rel="apple-touch-icon" sizes="180x180" href="/images/favicon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<meta name="robots" content="index, follow">

InformationDialog.razor

Read-only modal content (message + dismiss)—no destructive confirm step.

  • Path: CodeBlock.DevKit/src/1-Libraries/Source/Web.Blazor.Server/Components/InformationDialog.razor
  • Namespace / type: CodeBlock.DevKit.Web.Blazor.Server.ComponentsInformationDialog
@using Blazored.Modal
@using Blazored.Modal.Services
<div>
    <div class="alert alert-info">
        @((MarkupString)Message)
        <hr />
        <strong>@Localizer[CoreResource.Information_Dialog_Message]</strong>
    </div>

    <button @onclick="Continue" class="btn f-sm btn-success">@Localizer[CoreResource.Information_Dialog_Continue_Btn]</button>
    <button @onclick="Cancel" class="btn f-sm btn-warning">@Localizer[CoreResource.Information_Dialog_Cancel_Btn]</button>
</div>

@code {

    [CascadingParameter]
    BlazoredModalInstance ModalInstance { get; set; }

    [Parameter]
    public string Message { get; set; }


    async Task Continue() => await ModalInstance.CloseAsync(ModalResult.Ok(true));
    async Task Cancel() => await ModalInstance.CancelAsync();

}

Pagination.razor

Blazor pager UI (first, previous, next, page links) for lists that expose page index and total pages.

  • Path: CodeBlock.DevKit/src/1-Libraries/Source/Web.Blazor.Server/Components/Pagination.razor
  • Namespace / type: CodeBlock.DevKit.Web.Blazor.Server.ComponentsPagination
@using CodeBlock.DevKit.Core.Resources
@using Microsoft.AspNetCore.Components.Web
@inject IStringLocalizer<CoreResource> CoreLocalizer

<nav class="mt-2">
    <div class="container">
        <div class="row">
            <div class="w-100 overflow-auto">
                <ul class="pagination justify-content-center flex-nowrap" style="min-width: max-content;">
                    <li class="page-item @(CurrentPage > 1 ? "cursor-pointer" : "disabled")">
                        <span class="page-link" @onclick="() => SetCurrentPage(CurrentPage - 1)">&laquo;</span>
                    </li>

                    @if (ShowFirstPage)
                    {
                        <li class="cursor-pointer page-item @(CurrentPage == 1 ? "active" : "")">
                            <span class="page-link" @onclick="() => SetCurrentPage(1)">1</span>
                        </li>
                        @if (FirstEllipsisNeeded)
                        {
                            <li class="page-item disabled">
                                <span class="page-link">...</span>
                            </li>
                        }
                    }

                    @foreach (var visiblePages in VisiblePages)
                    {
                        <li class="cursor-pointer page-item @(visiblePages == CurrentPage ? "active" : "")">
                            <span class="page-link" @onclick="() => SetCurrentPage(visiblePages)">@visiblePages</span>
                        </li>
                    }

                    @if (ShowLastPage)
                    {
                        @if (LastEllipsisNeeded)
                        {
                            <li class="page-item disabled">
                                <span class="page-link">...</span>
                            </li>
                        }
                        <li class="cursor-pointer page-item @(CurrentPage == TotalPages ? "active" : "")">
                            <span class="page-link" @onclick="() => SetCurrentPage(TotalPages)">@TotalPages</span>
                        </li>
                    }

                    <li class="page-item @(CurrentPage < TotalPages ? "cursor-pointer" : "disabled")">
                        <span class="page-link" @onclick="() => SetCurrentPage(CurrentPage + 1)">&raquo;</span>
                    </li>
                </ul>
            </div>
        </div>
    </div>

    @if (ShowInfoArea)
    {
        <div class="bg-pagination-info-area p-2 border rounded-3 d-flex justify-content-between align-items-center">
            <span>@CoreLocalizer[CoreResource.Total_Records]: <strong>@TotalRecords</strong></span>
            <span>@CoreLocalizer[CoreResource.Page] <strong>@(TotalPages == 0 ? 0 : CurrentPage)</strong> @CoreLocalizer[CoreResource.Of] <strong>@TotalPages</strong></span>
        </div>
    }
</nav>


@code {
    [Parameter]
    public bool ShowInfoArea { get; set; } = true;

    [Parameter]
    public int CurrentPage { get; set; }

    [Parameter]
    public int RecordsPerPage { get; set; } = 10;

    [Parameter]
    public long TotalRecords { get; set; }

    [Parameter]
    public int MaxVisiblePages { get; set; } = 10;

    [Parameter]
    public EventCallback<int> PageChangedCallback { get; set; }

    protected virtual int TotalPages => (int)Math.Ceiling((double)TotalRecords / RecordsPerPage);

    protected virtual IEnumerable<int> VisiblePages => GetVisiblePages();

    protected virtual bool ShowFirstPage => VisiblePages.FirstOrDefault() > 2;
    protected virtual bool ShowLastPage => VisiblePages.LastOrDefault() < TotalPages - 1;

    protected virtual bool FirstEllipsisNeeded => VisiblePages.FirstOrDefault() > 2;
    protected virtual bool LastEllipsisNeeded => VisiblePages.LastOrDefault() < TotalPages - 1;

    protected virtual IEnumerable<int> GetVisiblePages()
    {
        var halfWindow = (MaxVisiblePages - 1) / 2;

        var startPage = Math.Max(1, CurrentPage - halfWindow);
        var endPage = Math.Min(TotalPages, CurrentPage + halfWindow);

        if (endPage - startPage + 1 < MaxVisiblePages)
        {
            if (startPage == 1)
            {
                endPage = Math.Min(TotalPages, startPage + MaxVisiblePages - 1);
            }
            else if (endPage == TotalPages)
            {
                startPage = Math.Max(1, endPage - MaxVisiblePages + 1);
            }
        }

        return Enumerable.Range(startPage, endPage - startPage + 1);
    }

    protected virtual async Task SetCurrentPage(int pageNumber)
    {
        if (pageNumber < 1 || pageNumber > TotalPages)
        {
            return;
        }

        CurrentPage = pageNumber;
        await PageChangedCallback.InvokeAsync(pageNumber);
    }
}

Redirect.razor

Performs client-side navigation to a given URL when the component runs—useful after logic that should not render a full page.

  • Path: CodeBlock.DevKit/src/1-Libraries/Source/Web.Blazor.Server/Components/Redirect.razor
  • Namespace / type: CodeBlock.DevKit.Web.Blazor.Server.ComponentsRedirect
@inject NavigationManager NavigationManager

@code
{
    [Parameter]
    public string Path { get; set; }

    protected override async Task OnInitializedAsync()
    {
        NavigationManager.NavigateTo(Path, true);
    }
}

RedirectToLogin.razor

Sends unauthenticated users to the configured login URL so protected areas do not render empty shells.

  • Path: CodeBlock.DevKit/src/1-Libraries/Source/Web.Blazor.Server/Components/RedirectToLogin.razor
  • Namespace / type: CodeBlock.DevKit.Web.Blazor.Server.ComponentsRedirectToLogin
@using CodeBlock.DevKit.Contracts.Models
@inject NavigationManager NavigationManager
@inject ApplicationSettings ApplicationSettings
@code
{
    protected override async Task OnInitializedAsync()
    {
        var loginPath ="/login";
        var returnUrlParameterName = "returnUrl";
        NavigationManager.NavigateTo($"{loginPath}?{returnUrlParameterName}=/{Uri.EscapeDataString(NavigationManager.ToBaseRelativePath(NavigationManager.Uri))}", true);
    }
}

SelectLanguage.razor

Culture picker for Blazor: lets users switch UI language and persist the choice (works with the rest of the localization stack).

  • Path: CodeBlock.DevKit/src/1-Libraries/Source/Web.Blazor.Server/Components/SelectLanguage.razor
  • Namespace / type: CodeBlock.DevKit.Web.Blazor.Server.ComponentsSelectLanguage
@using CodeBlock.DevKit.Web.Localization
@inject NavigationManager NavigationManager
@inject LocalizationSettings LocalizationSettings

@if (LocalizationSettings.HasMoreThanOneLanguage())
{
    <div id="@WrapperId" class="btn f-sm-group @WrapperCssClass">
        <button type="button" class="@BtnCssClass  dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
            @if (ShowIcon)
            {
                <i class="bi bi-globe-americas me-1 @IconCssClass"></i>
            }
            @if (ShowSelectedItem)
            {
                if (ShowShortName)
                {
                    <span class="selected-language-code @SelectedItemCssClass"> @LocalizationSettings.GetCurrentLanguageShortName()</span>
                }
                else
                {
                    <span class="selected-language-name @SelectedItemCssClass"> @LocalizationSettings.GetCurrentLanguageName()</span>
                }
            }
        </button>
        <ul class="dropdown-menu">
            @foreach (var item in LocalizationSettings.Languages)
            {
                <li><span class="dropdown-item cursor-pointer" @onclick="@(() => RedirectToSetCulturePage(item.Code))">@item.Name</span></li>
            }
        </ul>
    </div>
}

@code {
    [Parameter]
    public string BtnCssClass { get; set; } = "btn btn-outline-primary";

    [Parameter]
    public string SelectedItemCssClass { get; set; } = "f-xs";

    [Parameter]
    public string IconCssClass { get; set; } = "f-lg";

    [Parameter]
    public string WrapperCssClass { get; set; } = " m-0 p-0";

    [Parameter]
    public string WrapperId { get; set; } = "select-language";

    [Parameter]
    public bool ShowShortName { get; set; } = false;

    [Parameter]
    public bool ShowSelectedItem { get; set; } = true;

    [Parameter]
    public bool ShowIcon { get; set; } = false;

    protected virtual void RedirectToSetCulturePage(string languageCode)
    {
        var returnUrl = Uri.EscapeDataString(NavigationManager.ToBaseRelativePath(NavigationManager.Uri));

        var setCultureUrl = $"/setculture?lang={languageCode}&returnUrl=/{returnUrl}";
        NavigationManager.NavigateTo(setCultureUrl, true);
    }
}

SimpleLayout.razor

Minimal layout (small header/footer slot) for inner Blazor routes that should not use the full app chrome.

  • Path: CodeBlock.DevKit/src/1-Libraries/Source/Web.Blazor.Server/Components/SimpleLayout.razor
  • Namespace / type: CodeBlock.DevKit.Web.Blazor.Server.ComponentsSimpleLayout
@using Blazored.Toast
@using Blazored.Toast.Configuration
@using CodeBlock.DevKit.Contracts.Models
@using CodeBlock.DevKit.Web.Localization
@inject LocalizationSettings LocalizationSettings
@inject ApplicationSettings ApplicationSettings
@inject NavigationManager NavigationManager
@inherits LayoutComponentBase

<HeadContent>
    <link href="css/cb.general.spa.@(LocalizationSettings.GetCurrentLanguageDirection().ToLower()).min.css?v=1.6.0-beta5" rel="stylesheet" />
</HeadContent>

<SplashScreen BrandName="@ApplicationSettings.Localized.Name" BackgroundCssClass="simple-layout-dark-bg" ProgressBarCssClass="simple-layout-title-color" OnStarted="HandleSplashStarted"/>



@if (SplashScreenStarted)
{
    <BlazoredToasts Position="ToastPosition.BottomCenter"
                    Timeout="5"
                    IconType="IconType.Material"
                    ErrorIcon=""
                    InfoIcon=""
                    SuccessIcon=""
                    WarningIcon=""
                    ShowProgressBar="true" />

    <div class="simple-layout-wrapper">
        <div class="simple-fullscreen-bg"></div>

        <div class="bg-animation">
            <div class="floating-orb floating-orb-1"></div>
            <div class="floating-orb floating-orb-2"></div>
            <div class="floating-orb floating-orb-3"></div>
            <div class="floating-orb floating-orb-4"></div>
            <div class="floating-orb floating-orb-5"></div>
            <div class="floating-orb floating-orb-6"></div>
            <div class="floating-orb floating-orb-7"></div>
            <div class="floating-orb floating-orb-8"></div>
        </div>

        <div class="simple-language-wrapper">
            <SelectLanguage BtnCssClass="language-btn" />
        </div>

        <div class="simple-container">
            <div class="simple-wrapper wide">
                <div class="simple-branding">
                    <a href="/" class="simple-brand-link">
                        <img src="/images/logos/logo.png" alt="@ApplicationSettings.Localized.Name" class="simple-logo" />
                        <h1 class="simple-app-name">@ApplicationSettings.Localized.Name</h1>
                    </a>
                </div>

                <div class="simple-card wide">
                    @Body
                </div>
            </div>
        </div>
    </div>
}

@code {
    protected bool SplashScreenStarted = false;

    protected virtual void HandleSplashStarted()
    {
        SplashScreenStarted = true;
    }
}

SplashScreen.razor

First paint branding and loading overlay until the shell is ready—reduces layout shift on cold start.

  • Path: CodeBlock.DevKit/src/1-Libraries/Source/Web.Blazor.Server/Components/SplashScreen.razor
  • Namespace / type: CodeBlock.DevKit.Web.Blazor.Server.ComponentsSplashScreen
@using CodeBlock.DevKit.Core.Extensions
@if (!IsHidden)
{
    <div class="brand-splash-screen @BackgroundCssClass @(IsFadingOut ? "opacity-0" : "")">
        <div class="text-center w-100 px-3 splash-content @(IsContentVisible ? "show" : "")">
            @if (!LogoUrl.IsNullOrEmptyOrWhiteSpace() && IsContentVisible)
            {
                <img src="@LogoUrl" alt="@BrandName" class="splash-logo mb-3" style="max-width: 150px;" />
            }
            <div class="splash-brand-name mb-4 @BrandCssClass">@BrandNameToDisplay</div>
            <div class="@ProgressBarDisplayCssClass progress splash-progress" style="height: 25px;">
                <div class="progress-bar @ProgressBarCssClass" role="progressbar" style="width: @ProgressValue%" aria-valuemin="0" aria-valuemax="100"></div>
            </div>
        </div>
    </div>
}

@code {
    [Parameter] 
    public string BrandName { get; set; }
    [Parameter]
    public string LogoUrl { get; set; } = "/images/logos/logo.png";
    [Parameter] 
    public string BrandCssClass { get; set; } = "text-white";
    [Parameter] 
    public string BackgroundCssClass { get; set; } = "bg-dark-100";
    [Parameter]
    public string ProgressBarCssClass { get; set; } = "bg-warning";
    private string ProgressBarDisplayCssClass = "d-none";
    
    /// <summary>How long to keep the splash screen visible (default 3000 ms).</summary>
    [Parameter]
    public int DurationInMilliseconds { get; set; } = 1000;

    /// <summary>How fast the progress bar should fill up (default 2000 ms).</summary>
    [Parameter]
    public int ProgressAnimationMilliseconds { get; set; } = 900;

    /// <summary>Delay before showing the content with fade-in animation (default 200 ms).</summary>
    [Parameter]
    public int ContentFadeInDelayMilliseconds { get; set; } = 200;

    /// <summary>Called when splash screen starts (immediately on first render).</summary>
    [Parameter]
    public EventCallback OnStarted { get; set; }

    /// <summary>Called after splash screen is fully hidden (including fade out).</summary>
    [Parameter]
    public EventCallback OnClosed { get; set; }

    private int ProgressValue = 0;
    private bool IsFadingOut = false;
    private bool IsHidden = false;
    private bool IsContentVisible = false;
    private string BrandNameToDisplay = "";
    
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            // Invoke OnStarted callback immediately when splash screen starts
            await OnStarted.InvokeAsync();
            
            StartSplashProgress();
            StartContentFadeIn();

            await Task.Delay(DurationInMilliseconds);
            IsFadingOut = true;
            StateHasChanged();

            await Task.Delay(300); // fade-out duration
            IsHidden = true;
            await InvokeAsync(StateHasChanged);
            await OnClosed.InvokeAsync();
        }
    }

    private void StartContentFadeIn()
    {
        _ = Task.Run(async () =>
        {
            await Task.Delay(ContentFadeInDelayMilliseconds);
            
            // Set up the content before making it visible
            BrandNameToDisplay = BrandName;
            ProgressBarDisplayCssClass = "";
            await InvokeAsync(StateHasChanged);
            
            // Small delay to let the content render, then start animation
            await Task.Delay(50);
            IsContentVisible = true;
            await InvokeAsync(StateHasChanged);
        });
    }

    private void StartSplashProgress()
    {
        _ = Task.Run(async () =>
        {
            int steps = ProgressAnimationMilliseconds / 100;
            for (int i = 0; i <= steps; i++)
            {
                ProgressValue = (int)((i / (double)steps) * 100);
                await InvokeAsync(StateHasChanged);
                await Task.Delay(100);
            }
        });
    }
}

_Imports.razor

Project-wide @using directives for the shared component library so each .razor file stays short and consistent.

  • Path: CodeBlock.DevKit/src/1-Libraries/Source/Web.Blazor.Server/Components/_Imports.razor
  • Namespace / type: CodeBlock.DevKit.Web.Blazor.Server.Components_Imports
@using CodeBlock.DevKit.Core.Resources
@using Microsoft.AspNetCore.Identity
@using Microsoft.Extensions.Localization
@using System.Net.Http
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop

@inject IStringLocalizer<CoreResource> Localizer

See also