Table of Contents

Customizing Blazor UI

Use this guide when you want to change a pre-built Blazor surface: .razor UI pages (with @page), UI components, or UI layouts. You typically inherit the DevKit or module type and mark it with ReplaceBaseComponent.

If you are editing Razor .cshtml instead, see Customizing Razor UI.


Find what you are customizing

If the UI is… Look here first
Shared across features (confirmation UI components, pagination UI components, simple UI layout, error UI page, culture UI page / UI component, and similar) UI components module — full list, type names, paths, and source.
Owned by a product module (login, plans, checkout, settings UI pages, and so on) That module’s UI article under Modules — for example Pricing UI. It lists which files exist and how replacement works for that module.
Admin or user panel (navigation UI components, headers, UI layout for the area) Admin panel or Website & user panel.

More module UI links: Administration · AI ChatBot · Analytic · Blogging · Change tracking · Identity · Licensing · Monitoring · Payment · Pricing · Settings · Subscription · Supporting UI components


ReplaceBaseComponent (components and pages)

For .razor UI components (including UI pages with @page):

  1. Add a new UI component in your client project.
  2. Inherit the DevKit or module UI component you are replacing.
  3. Add @attribute [ReplaceBaseComponent] so the host picks yours instead of the base.
  4. Copy the original markup from the UI documentation and edit, or reimplement while keeping the same contract.

Example from the template: a custom user-panel navigation UI component inherits the base UI component and replaces it.

NavMenu.razor in the SaaS template

@using CodeBlock.DevKit.Clients.WebApp.Pages.UserPanel.Shared
@inherits MainNavMenu
@attribute [ReplaceBaseComponent]
@inject ApplicationSettings ApplicationSettings

<nav class="sidebar-nav">
    <ul class="nav flex-column">
        <li class="nav-item">
            <NavLink @onclick="CloseNavMenu" class="nav-link" href="/dashboard" Match="NavLinkMatch.All">
                <i class="bi bi-speedometer2"></i>
                <span>@WebAppClientLocalizer[WebAppClientResource.Overview]</span>
            </NavLink>
        </li>
        <li class="nav-item">
            <NavLink @onclick="CloseNavMenu" class="nav-link" href="/user/subscriptions">
                <i class="bi bi-calendar-check"></i>
                <span>@WebAppClientLocalizer[WebAppClientResource.Nav_Menu_Subscriptions]</span>
            </NavLink>
        </li>
        <li class="nav-item">
            <NavLink @onclick="CloseNavMenu" class="nav-link" href="/user/licenses">
                <i class="bi bi-shield-check"></i>
                <span>@WebAppClientLocalizer[WebAppClientResource.Nav_Menu_Licenses]</span>
            </NavLink>
        </li>
        <li class="nav-item">
            <NavLink @onclick="CloseNavMenu" class="nav-link" href="/user/bots">
                <i class="bi bi-robot"></i>
                <span>@WebAppClientLocalizer[WebAppClientResource.Bots]</span>
            </NavLink>
        </li>
        <li class="nav-item">
            <NavLink @onclick="CloseNavMenu" class="nav-link" href="/user/orders">
                <i class="bi bi-receipt"></i>
                <span>@WebAppClientLocalizer[WebAppClientResource.Nav_Menu_Orders]</span>
            </NavLink>
        </li>
        <li class="nav-item">
            <NavLink @onclick="CloseNavMenu" class="nav-link" href="/user/profile">
                <i class="bi bi-person"></i>
                <span>@WebAppClientLocalizer[WebAppClientResource.Nav_Menu_User_Profile]</span>
            </NavLink>
        </li>
        
        <li class="nav-item mt-auto">
            <a href="/" target="_blank" class="nav-link">
                <i class="bi bi-globe"></i>
                <span>@WebAppClientLocalizer[WebAppClientResource.Nav_Menu_Visit_Website]</span>
            </a>
        </li>
        <li class="nav-item">
            <NavLink @onclick="CloseNavMenu" class="nav-link text-danger" href="/logout">
                <i class="bi bi-box-arrow-right"></i>
                <span>@WebAppClientLocalizer[WebAppClientResource.Nav_Menu_Logout]</span>
            </NavLink>
        </li>
    </ul>
</nav>

UI layouts are .razor UI components used as layouts—you replace them with the same inherit + ReplaceBaseComponent pattern (see Layouts).


Layouts

A UI layout wraps UI pages and UI components (navigation bars, headers, footers). For Blazor, treat a layout like any other replaceable .razor type.

Where to look

UI layout kind Where it is documented
General-purpose Blazor UI layout (SimpleLayout, and similar) Blazor components (SimpleLayout.razor)
Module or client Blazor UI layouts (admin shell, user panel, pricing, licensing, AI chatbot, …) Tables below + the module UI article—for example Identity UI, Pricing UI
Public website UI layout in the template Edited directly in the template repo — see Public website UI layout below

Quick steps

  1. Create a UI layout in your client project (a .razor UI component used as the layout).
  2. Inherit the DevKit or module UI layout you are replacing.
  3. Add @attribute [ReplaceBaseComponent].
  4. Adjust markup or structure as needed.

Blazor layouts (.razor)

Use these types as the inherit target when you replace a UI layout (namespace + type from DevKit).

Type (namespace) Role
CodeBlock.DevKit.Web.Blazor.Server.Components.SimpleLayout Minimal Blazor UI layout.
CodeBlock.DevKit.Clients.AdminPanel.Pages.Shared.AdminPanelLayout Admin panel UI layout.
CodeBlock.DevKit.Clients.WebApp.Pages.UserPanel.Shared.UserPanelLayout Signed-in user panel UI layout.
CodeBlock.DevKit.Pricing.UI.Pages.Shared.PricingLayout Pricing UI layout for plans and checkout UI pages.
CodeBlock.DevKit.Licensing.UI.Pages.Shared.LicensingLayout Licensing UI layout.
CodeBlock.DevKit.AIChatBot.UI.Pages.Shared.AIChatBotClientLayout AI chatbot client UI layout.

SimpleLayout.razor

@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;
    }
}

AdminPanelLayout.razor

@using Blazored.Toast
@using Blazored.Toast.Configuration
@using CodeBlock.DevKit.AIChatBot.UI.Pages.Client.Components
@using CodeBlock.DevKit.Web.Localization
@using CodeBlock.DevKit.Clients.AdminPanel.Pages.Shared
@inject LocalizationSettings LocalizationSettings
@inject ApplicationSettings ApplicationSettings
@inherits LayoutComponentBase


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

<SplashScreen BrandName="@ApplicationSettings.Localized.Name" 
              BackgroundCssClass="bg-dark" 
              BrandCssClass="text-white"
              ProgressBarCssClass="bg-warning"
              DurationInMilliseconds="1500"
              ProgressAnimationMilliseconds="1200"
              OnStarted="HandleSplashStarted" 
              LogoUrl="/images/logos/logo.png" />

<BlazoredToasts Position="ToastPosition.BottomCenter"
                Timeout="5"
                IconType="IconType.Material"
                ErrorIcon=""
                InfoIcon=""
                SuccessIcon=""
                WarningIcon=""
                ShowProgressBar="true" />

@if (SplashScreenStarted)
{
    <div class="page" id="admin-page-wrapper">
        <div class="sidebar">
            <MainNavMenu />
        </div>
        <main>
            <div class="top-row px-4">
                <SelectLanguage BtnCssClass="btn text-dark m-1 p-0 borderless" ShowIcon="true" ShowSelectedItem="true" ShowShortName="true" />
                <TopMenu BtnCssClass="text-dark" />
            </div>

            <article class="content px-4 mt-3">
                @Body
            </article>
        </main>
    </div>
    
    <HelpButton />
}

@code {
    bool SplashScreenStarted = false;

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

UserPanelLayout.razor

@using Blazored.Toast
@using Blazored.Toast.Configuration
@using CodeBlock.DevKit.AIChatBot.UI.Models
@using CodeBlock.DevKit.AIChatBot.UI.Pages.Client.Components
@using CodeBlock.DevKit.Web.Localization
@inject LocalizationSettings LocalizationSettings
@inherits LayoutComponentBase
@inject ApplicationSettings ApplicationSettings

<HeadContent>
    <link href="css/cb.user.panel.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" LogoUrl="/images/logos/logo.png" />

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

    <header class="top-header">
        <div class="container-fluid">
            <div class="d-flex justify-content-between align-items-center">
                <div class="d-flex align-items-center">
                    <button class="btn btn-link text-white p-0 me-3 mobile-menu-btn d-lg-none" id="mobileMenuBtn">
                        <i class="bi bi-list fs-4"></i>
                    </button>
                    <a href="/dashboard" class="logo">
                        <img src="/images/logos/logo.png" alt="@ApplicationSettings.Localized.Name" class="logo-image me-2">
                        @ApplicationSettings.Localized.Name
                    </a>
                </div>
                <div class="d-flex align-items-center gap-3">

                    <CustomSelectLanguage />

                    <TopMenu />
                </div>
            </div>
        </div>
    </header>

    <div class="main-container">
        <div class="mobile-overlay" id="mobileOverlay"></div>

        <aside class="sidebar" id="sidebar">
            <MainNavMenu />
        </aside>

        <main class="main-content">
            @Body
        </main>

        @if (SettingAccessorService.Settings.AIChatBot.ShowChatBotWidgetInUserPanel)
        {
            <ChatBotWidget Theme="ChatBotWidgetTheme.Dark" />
        }

    </div>
}

@code {

    protected bool SplashScreenStarted = false;

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

PricingLayout.razor

@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"/>

<BlazoredToasts Position="ToastPosition.BottomCenter"
                Timeout="5"
                IconType="IconType.Material"
                ErrorIcon=""
                InfoIcon=""
                SuccessIcon=""
                WarningIcon=""
                ShowProgressBar="true" />

@if (SplashScreenStarted)
{
    <div class="simple-layout-wrapper">
        <!-- Fullscreen Background -->
        <div class="simple-fullscreen-bg"></div>

        <!-- Animated Background -->
        <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>

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


        <!-- New Flexible Layout for Order Pages -->
        <div class="simple-container flexible">
            <div class="simple-wrapper full-width">
                <!-- App Branding -->
                <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>

                <!-- Main Content - No Card Wrapper for Full Flexibility -->
                <div class="simple-content-area">
                    @Body
                </div>
            </div>
        </div>

    </div>
}

@code {
    bool SplashScreenStarted = false;

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

LicensingLayout.razor

@inherits LayoutComponentBase

@Body

@code{
    // This file is intentionally left blank. It is used to override the default layout for Licensing UI pages.
}

AIChatBotClientLayout.razor

@using Blazored.Toast
@using Blazored.Toast.Configuration
@using CodeBlock.DevKit.AIChatBot.UI.Pages.Client.Components
@using CodeBlock.DevKit.Web.Localization
@inject LocalizationSettings LocalizationSettings
@inherits LayoutComponentBase

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


<BlazoredToasts Position="ToastPosition.BottomCenter"
                Timeout="5"
                IconType="IconType.Material"
                ErrorIcon=""
                InfoIcon=""
                SuccessIcon=""
                WarningIcon=""
                ShowProgressBar="true" />

@Body

Choosing a UI layout

  • Admin appAdminPanelLayout.razor
  • User account areaUserPanelLayout.razor
  • PricingPricingLayout.razor
  • LicensingLicensingLayout.razor
  • AI chatbot (client)AIChatBotClientLayout.razor
  • Public website (template)Public website UI layout

Razor authentication shells use .cshtml UI layouts (_AuthenticationLayout, _SimpleLayout) — see Customizing Razor UI — Layouts.

Public website UI layout (template)

The public website UI layout is not always replaced the same way as the user panel UI layout: in the SaaS template you often edit it in place:

WebsiteLayout.razor

@using CodeBlock.DevKit.AIChatBot.UI.Models
@using CodeBlock.DevKit.Contracts.Services
@inject LocalizationSettings LocalizationSettings
@inherits LayoutComponentBase
@inject ApplicationSettings ApplicationSettings
@inject ISettingAccessorService SettingAccessorService

<HeadContent>
    <link href="css/cb.website.spa.@(LocalizationSettings.GetCurrentLanguageDirection().ToLower()).min.css" rel="stylesheet" />
</HeadContent>

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

<div id="main-layout-wrapper" class="website-wrapper">
    <BlazoredToasts Position="ToastPosition.BottomCenter"
                    Timeout="5"
                    IconType="IconType.Material"
                    ErrorIcon=""
                    InfoIcon=""
                    SuccessIcon=""
                    WarningIcon=""
                    ShowProgressBar="true" />

    <NavMenu />

    <main>
        @Body
    </main>

    <Footer />

    @if (SettingAccessorService.Settings.AIChatBot.ShowChatBotWidgetOnWebsite)
    {
        <div class="chat-widget">
            <ChatBotWidget Theme="ChatBotWidgetTheme.Dark" />
        </div>
    }

</div>

How module UI is loaded (Blazor routes)

Module assemblies are registered in each client’s startup. Blazor also discovers extra routes via Router AdditionalAssemblies.

WebApp startup (module registration):

using CanBeYours.Infrastructure;
using CodeBlock.DevKit.Clients.WebApp;

namespace CanBeYours.WebApp;

/// <summary>
/// Static configuration class for the WebApp client application.
/// This class demonstrates how to configure services and middleware pipeline using CodeBlock.DevKit.
/// The current setup shows a basic configuration - you can extend this with your own services,
/// authentication, logging, or other middleware while maintaining the same structure.
/// </summary>
internal static class Startup
{
    /// <summary>
    /// Configures the application services including WebApp client module and infrastructure.
    /// This method demonstrates how to add CodeBlock.DevKit modules and your own services.
    /// </summary>
    /// <param name="builder">The web application builder instance</param>
    /// <returns>Configured web application instance</returns>
    public static WebApplication ConfigureServices(this WebApplicationBuilder builder)
    {
        builder.AddWebAppClientModule(typeof(Startup));
        builder.Services.AddInfrastructureModule();

        return builder.Build();
    }

    /// <summary>
    /// Configures the application middleware pipeline including WebApp client module and infrastructure.
    /// This method demonstrates how to set up the request processing pipeline.
    /// </summary>
    /// <param name="app">The web application instance</param>
    /// <returns>Configured web application instance</returns>
    public static WebApplication ConfigurePipeline(this WebApplication app)
    {
        app.UseWebAppClientModule();
        app.Services.UseInfrastructureModule();
        return app;
    }
}

WebApp router (App.razor):

@* 
 * Main application component for the WebApp client.
 * This component demonstrates how to set up the root application structure using CodeBlock.DevKit.
 * It includes routing, authentication, modal support, and layout management.
 * The current functionality serves as a learning example - you can customize the routing,
 * authentication flow, and error handling while maintaining the same structure.
 *@
@using CodeBlock.DevKit.Web.Blazor.Server
@using CodeBlock.DevKit.Web.Blazor.Server.Components
@using CanBeYours.WebApp.Pages.Website.Shared
@inject IModuleRegistry ModuleRegistry
@inherits AppBase

<CascadingBlazoredModal FocusFirstElement="false" HideCloseButton="false" ContentScrollable="true" DisableBackgroundCancel="true" Animation="@ModalAnimation.FadeIn(0.4)">
    <CascadingAuthenticationState>
        <Router AppAssembly="@typeof(Program).Assembly" AdditionalAssemblies="@ModuleRegistry.GetUIModuleAssemblies()">
            <Found Context="routeData">
                <CustomAuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(WebsiteLayout)">
                    <Authorizing>
                        <LayoutView Layout="@typeof(SimpleLayout)">
                            <Alert ShowLinkedButtons=false Title="@CoreLocalizer[CoreResource.Error_Authorization_Title]" Message="@CoreLocalizer[CoreResource.Error_Authorization_Message]" />
                        </LayoutView>
                    </Authorizing>
                    <NotAuthorized>
                        <LayoutView Layout="@typeof(SimpleLayout)">
                            <RedirectToLogin />
                        </LayoutView>
                    </NotAuthorized>
                </CustomAuthorizeRouteView>
            </Found>
            <NotFound>
                <LayoutView Layout="@typeof(SimpleLayout)">
                    <Alert Title="@CoreLocalizer[CoreResource.Error_NotFound_Title]" Message="@CoreLocalizer[CoreResource.Error_NotFound_Message]" />
                </LayoutView>
            </NotFound>
        </Router>
    </CascadingAuthenticationState>
</CascadingBlazoredModal>

For Razor .cshtml UI pages from modules (path override, no AdditionalAssemblies route table), see How module UI is loaded in the Razor guide.