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):
- Add a new UI component in your client project.
- Inherit the DevKit or module UI component you are replacing.
- Add
@attribute [ReplaceBaseComponent]so the host picks yours instead of the base. - 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
- Create a UI layout in your client project (a
.razorUI component used as the layout). - Inherit the DevKit or module UI layout you are replacing.
- Add
@attribute [ReplaceBaseComponent]. - 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 app →
AdminPanelLayout.razor - User account area →
UserPanelLayout.razor - Pricing →
PricingLayout.razor - Licensing →
LicensingLayout.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:
@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.
Related topics
- Customizing Razor UI —
.cshtmlpath overrides and Razor UI layouts. - Blazor components — embedded sources for shared
.razoritems. - Building UI in your product — where your feature folders live in the template.