<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:a10="http://www.w3.org/2005/Atom" version="2.0">
  <channel xmlns:media="http://search.yahoo.com/mrss/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <title>ABP.IO Stories</title>
    <link>https://abp.io/community/articles</link>
    <description>A hub for ABP Framework, .NET, and software development. Access articles, tutorials, news, and contribute to the ABP community.</description>
    <lastBuildDate>Sun, 04 Oct 2026 01:36:15 Z</lastBuildDate>
    <generator>Community - ABP.IO</generator>
    <image>
      <url>https://abp.io/assets/favicon.ico/favicon-32x32.png</url>
      <title>ABP.IO Stories</title>
      <link>https://abp.io/community/articles</link>
    </image>
    <a10:link rel="self" type="application/rss+xml" title="self" href="https://abp.io/community/rss?member=jonathanpotts" />
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/customizing-the-abp-basic-theme-with-bootswatch-4luoqzr0</guid>
      <link>https://abp.io/community/posts/customizing-the-abp-basic-theme-with-bootswatch-4luoqzr0</link>
      <a10:author>
        <a10:name>jonathanpotts</a10:name>
        <a10:uri>https://abp.io/community/members/jonathanpotts</a10:uri>
      </a10:author>
      <category>customization</category>
      <category>basic-theme</category>
      <category>Bootswatch</category>
      <title>Customizing the ABP Basic Theme with Bootswatch</title>
      <description>While the ABP Basic Theme is a good starting point for your ABP app, you may want to add some more flair to it. We can accomplish this with Bootswatch.</description>
      <pubDate>Fri, 28 Jan 2022 22:18:06 Z</pubDate>
      <a10:updated>2026-10-03T21:14:20Z</a10:updated>
      <content:encoded><![CDATA[<h1>Customizing the ABP Basic Theme with Bootswatch</h1>
<p>The source code for this project can be found in the <a href="https://github.com/jonathanpotts/abp-bootswatch">jonathanpotts/abp-bootswatch GitHub repo</a>.</p>
<p>While the ABP Basic Theme is a good starting point for your ABP app, you may want to add some more flair to it. We can accomplish this with Bootswatch.</p>
<h2>What is Bootswatch?</h2>
<p><a href="https://bootswatch.com/">Bootswatch</a> is a collection of free open-source Bootstrap themes that act as slot-in replacements for Bootstrap. The website includes demos of how the different Bootstrap components look when styled by each of the themes.</p>
<h2>Getting started</h2>
<p>This article assumes you have the <a href="https://dot.net/">.NET</a> SDK and <a href="https://nodejs.org/">Node.js</a> installed. Also, while not required, it is recommended to have <a href="https://yarnpkg.com/">Yarn</a> installed.</p>
<p>Let's start by creating a new ABP project that we will add Bootswatch to.</p>
<p>Open a terminal. We will use the ABP CLI to create the project.</p>
<p>To make sure you have the ABP CLI installed and that it is up to date, run the following command:</p>
<pre><code>dotnet tool update -g Volo.Abp.Cli
</code></pre>
<p>Now, navigate to the directory you would like the project to be in, and run the following command to create the project (assuming you are on Windows and have <a href="https://docs.microsoft.com/sql/database-engine/configure-windows/sql-server-express-localdb">SQL Server Express LocalDB</a> installed):</p>
<pre><code>abp new BootswatchDemo
</code></pre>
<p>If you do not have SQL Server Express LocalDB installed (or are on Linux or Mac), you can use a different database management system (DBMS) such as SQLite by using this command instead:</p>
<pre><code>abp new BootswatchDemo -dbms SQLite
</code></pre>
<p>We should now have a starter app to work with in our directory.</p>
<p>Let's run the <strong>BootswatchDemo.DbMigrator</strong> project to setup the app's database. You can do this in the terminal while in the <strong>BootswatchDemo.DbMigrator</strong> project directory by running the following command:</p>
<pre><code>dotnet run
</code></pre>
<p>Now, we can run the <strong>BootswatchDemo.Web</strong> project to see what the app looks like without any modifications.</p>
<p>If you open the app in your browser, you should get something like this:</p>
<p><img src="https://raw.githubusercontent.com/jonathanpotts/AbpBootswatchDemo/main/BootswatchDemo-Default.png" alt="ABP Basic Theme" /></p>
<p>Let's stop the app for now.</p>
<h2>Installing Bootswatch</h2>
<p>We need to add Bootswatch to the <strong>package.json</strong> file in the <strong>BootswatchDemo.Web</strong> project directory. You can do this in the terminal while in the <strong>BootswatchDemo.Web</strong> project directory by running the following command (if you have yarn installed):</p>
<pre><code>yarn add bootswatch
</code></pre>
<p>If you do not have yarn installed, you can instead use npm by running the following command:</p>
<pre><code>npm install bootswatch
</code></pre>
<p>Now that we have Bootswatch added to the package.json file, we need to tell ABP that it is a client-side library that we are using.</p>
<p>To do this, we need to modify the <strong>abp.resourcemapping.js</strong> file in the <strong>BootswatchDemo.Web</strong> project directory to make it match the following:</p>
<pre><code class="language-js">module.exports = {
    aliases: {
        &quot;@node_modules&quot;: &quot;./node_modules&quot;,
        &quot;@libs&quot;: &quot;./wwwroot/libs&quot;
    },
    clean: [
        &quot;@libs&quot;
    ],
    mappings: {
        &quot;@node_modules/bootswatch/dist/**/*.css&quot;: &quot;@libs/bootswatch/&quot;,
    }
};
</code></pre>
<p>This tells ABP that the Bootswatch CSS files (located in the ./node_modules/bootswatch/dist/ directory) should be copied to the ./wwwroot/libs/bootswatch/ directory in the BootswatchDemo.Web project directory. Additionally, it will have ABP clean the ./wwwroot/libs directory each time it processes the client-side libraries so that it's always up to date and doesn't contain old files that aren't being used anymore.</p>
<p>Now that we've done this, we need to have ABP process the Bootswatch files. You can do this in the terminal while in the <strong>BootswatchDemo.Web</strong> project directory by running the following command:</p>
<pre><code>abp install-libs
</code></pre>
<p>Bootswatch should now be installed into our project and we just need to wire it up to the ABP Basic Theme.</p>
<h2>Customizing the ABP Basic Theme with Bootswatch</h2>
<p>Right now, the Bootswatch files should be in our project, but our app does not use them. To change this, we need to customize the ABP Basic Theme.</p>
<p>Inside the <strong>BootswatchDemo.Web</strong> project directory, create a sub-directory called <strong>Bundling</strong>. Inside the <strong>Bundling</strong> directory, create a file called <strong>BootswatchStyleContributor.cs</strong>.</p>
<p>The contents of the <strong>BootswatchStyleContributor.cs</strong> file should be:</p>
<pre><code class="language-cs">using System.Collections.Generic;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;

namespace BootswatchDemo.Web.Bundling
{
    public class BootswatchStyleContributor : BundleContributor
    {
        public override void ConfigureBundle(BundleConfigurationContext context)
        {
            var theme = &quot;darkly&quot;;

            context.Files.ReplaceOne(
                &quot;/libs/bootstrap/css/bootstrap.css&quot;,
                $&quot;/libs/bootswatch/{theme}/bootstrap.css&quot;
            );
        }
    }
}
</code></pre>
<p>As you can see from the code, we are creating a BundleContributor that tells ABP to replace Bootstrap with a Bootswatch theme (in this case, we are using the Darkly theme).</p>
<p>Now we need to tell ABP about the <strong>BootswatchStyleContributor</strong>. To do this, open the <strong>BootswatchDemoWebModule.cs</strong> file in the <strong>BootswatchDemo.Web</strong> project directory.</p>
<p>We need to add a using statement to the top of the file:</p>
<pre><code class="language-cs">using BootswatchDemo.Web.Bundling;
</code></pre>
<p>We also need to replace the <code>ConfigureBundles()</code> method with the following:</p>
<pre><code class="language-cs">    private void ConfigureBundles()
    {
        Configure&lt;AbpBundlingOptions&gt;(options =&gt;
        {
            options.StyleBundles.Configure(
                BasicThemeBundles.Styles.Global,
                bundle =&gt;
                {
                    bundle.AddFiles(&quot;/global-styles.css&quot;);
                    bundle.AddContributors(typeof(BootswatchStyleContributor));
                }
            );
        });
    }
</code></pre>
<p>Now that ABP is using our BundleContributor, we can run the <strong>BootswatchDemo.Web</strong> project and we should be able to see the Darkly Bootswatch Theme being used.</p>
<p>If you open the app in your browser, you should get something like this:</p>
<p><img src="https://raw.githubusercontent.com/jonathanpotts/AbpBootswatchDemo/main/BootswatchDemo-Darkly.png" alt="ABP Basic Theme with Darkly Bootswatch Theme" /></p>
<p>Now while it looks different, it doesn't really match what the Bootswatch gallery shows for the Darkly theme. This is because of some assumptions that the ABP Basic Theme makes, but we can fix this.</p>
<p>Let's stop the app for now.</p>
<h2>Overriding Layouts and Components in the ABP Basic Theme</h2>
<p>We need to override some of the layouts and components of the ABP Basic Theme to make our app match how the Bootswatch themes are supposed to look.</p>
<p>To do this, we will put modified versions of the ABP Basic Theme files that need to be overridden in the <strong>BootswatchDemo.Web</strong> project directory. By matching the same directory structure as the ABP Basic Theme, the files in the project will override the built-in ABP Basic Theme files.</p>
<p>For reference, the source code for the ABP Basic Theme is available in the abpframework/abp GitHub repository under <a href="https://github.com/abpframework/abp/tree/dev/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic">./modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic</a>.</p>
<p>We need to add the following files to the <strong>BootswatchDemo.Web</strong> project directory which are modified from the source code for the ABP Basic Theme:</p>
<p><strong>Themes/Basic/Components/MainNavbar/Default.cshtml</strong>:</p>
<pre><code class="language-cs">@using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Themes.Basic.Components.Brand
@using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Themes.Basic.Components.Menu
@using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Themes.Basic.Components.Toolbar
&lt;nav class=&quot;navbar navbar-expand-md navbar-dark bg-primary shadow-sm flex-column flex-md-row mb-4&quot; id=&quot;main-navbar&quot; style=&quot;min-height: 4rem;&quot;&gt;
    &lt;div class=&quot;container&quot;&gt;
        @(await Component.InvokeAsync&lt;MainNavbarBrandViewComponent&gt;())
        &lt;button class=&quot;navbar-toggler&quot; type=&quot;button&quot; data-bs-toggle=&quot;collapse&quot;
                data-bs-target=&quot;#main-navbar-collapse&quot; aria-controls=&quot;main-navbar-collapse&quot;
                aria-expanded=&quot;false&quot; aria-label=&quot;Toggle navigation&quot;&gt;
            &lt;span class=&quot;navbar-toggler-icon&quot;&gt;&lt;/span&gt;
        &lt;/button&gt;
        &lt;div class=&quot;collapse navbar-collapse&quot; id=&quot;main-navbar-collapse&quot;&gt;
            &lt;ul class=&quot;navbar-nav mx-auto&quot;&gt;
                @(await Component.InvokeAsync&lt;MainNavbarMenuViewComponent&gt;())
            &lt;/ul&gt; 
            &lt;ul class=&quot;navbar-nav&quot;&gt; 
                @(await Component.InvokeAsync&lt;MainNavbarToolbarViewComponent&gt;())
            &lt;/ul&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/nav&gt;
</code></pre>
<p>In this file, we changed the <code>nav</code> element to use the <code>bg-primary</code> class instead of the <code>bg-dark</code> class.</p>
<blockquote>
<p><strong>Notice:</strong> As of ABP Framework 5.1.4, the next two files (<strong>Account.cshtml</strong> and <strong>Application.cshtml</strong>) do not need to be added.</p>
</blockquote>
<p><strong>Themes/Basic/Layouts/Account.cshtml</strong>:</p>
<pre><code class="language-cs">@using Microsoft.Extensions.Localization
@using Microsoft.Extensions.Options
@using Volo.Abp.AspNetCore.MultiTenancy
@using Volo.Abp.AspNetCore.Mvc.UI.Components.LayoutHook
@using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Bundling
@using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Themes.Basic.Components.MainNavbar
@using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Themes.Basic.Components.PageAlerts
@using Volo.Abp.AspNetCore.Mvc.UI.Theming
@using Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetScripts
@using Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetStyles
@using Volo.Abp.MultiTenancy
@using Volo.Abp.Localization
@using Volo.Abp.Ui.Branding
@inject IBrandingProvider BrandingProvider
@inject IOptions&lt;AbpMultiTenancyOptions&gt; MultiTenancyOptions
@inject ICurrentTenant CurrentTenant
@inject IStringLocalizer&lt;AbpUiMultiTenancyResource&gt; MultiTenancyStringLocalizer
@inject ITenantResolveResultAccessor TenantResolveResultAccessor

@{
    Layout = null;
    var containerClass = ViewBag.FluidLayout == true ? &quot;container-fluid&quot; : &quot;container&quot;; //TODO: Better and type-safe options
    var rtl = CultureHelper.IsRtl ? &quot;rtl&quot; : string.Empty;
}

&lt;!DOCTYPE html&gt;

&lt;html lang=&quot;@CultureInfo.CurrentCulture.Name&quot; dir=&quot;@rtl&quot;&gt;
&lt;head&gt;
    @await Component.InvokeLayoutHookAsync(LayoutHooks.Head.First, StandardLayouts.Account)

    &lt;meta charset=&quot;utf-8&quot;&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1, shrink-to-fit=no&quot;&gt;
    &lt;meta http-equiv=&quot;X-UA-Compatible&quot; content=&quot;ie=edge&quot;&gt;

    &lt;title&gt;@(ViewBag.Title == null ? BrandingProvider.AppName : ViewBag.Title)&lt;/title&gt;

    @if (ViewBag.Description != null)
    {
        &lt;meta name=&quot;description&quot; content=&quot;@(ViewBag.Description as string)&quot; /&gt;
    }
    &lt;abp-style-bundle name=&quot;@BasicThemeBundles.Styles.Global&quot; /&gt;

    @await RenderSectionAsync(&quot;styles&quot;, false)

    @await Component.InvokeAsync(typeof(WidgetStylesViewComponent))

    @await Component.InvokeLayoutHookAsync(LayoutHooks.Head.Last, StandardLayouts.Account)
&lt;/head&gt;
&lt;body class=&quot;abp-account-layout @rtl&quot;&gt;
    @await Component.InvokeLayoutHookAsync(LayoutHooks.Body.First, StandardLayouts.Account)

    @(await Component.InvokeAsync&lt;MainNavbarViewComponent&gt;())

    &lt;div class=&quot;@containerClass&quot;&gt;
        &lt;abp-row&gt;
            &lt;abp-column class=&quot;col mx-auto&quot; style=&quot;max-width: 440px&quot;&gt;
                @if (MultiTenancyOptions.Value.IsEnabled &amp;&amp;
                  (TenantResolveResultAccessor.Result?.AppliedResolvers?.Contains(CookieTenantResolveContributor.ContributorName) == true ||
                   TenantResolveResultAccessor.Result?.AppliedResolvers?.Contains(QueryStringTenantResolveContributor.ContributorName) == true))
                {
                    &lt;div class=&quot;card shadow-sm rounded mb-3&quot;&gt;
                        &lt;div class=&quot;card-body px-5&quot;&gt;
                            &lt;div class=&quot;row&quot;&gt;
                                &lt;div class=&quot;col&quot;&gt;
                                    &lt;span style=&quot;font-size: .8em;&quot; class=&quot;text-uppercase text-muted&quot;&gt;@MultiTenancyStringLocalizer[&quot;Tenant&quot;]&lt;/span&gt;&lt;br /&gt;
                                    &lt;h6 class=&quot;m-0 d-inline-block&quot;&gt;
                                        @if (CurrentTenant.Id == null)
                                        {
                                            &lt;span&gt;
                                                @MultiTenancyStringLocalizer[&quot;NotSelected&quot;]
                                            &lt;/span&gt;
                                        }
                                        else
                                        {
                                            &lt;strong&gt;@(CurrentTenant.Name ?? CurrentTenant.Id.Value.ToString())&lt;/strong&gt;
                                        }
                                    &lt;/h6&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;col-auto&quot;&gt;
                                    &lt;a id=&quot;AbpTenantSwitchLink&quot; href=&quot;javascript:;&quot; class=&quot;btn btn-sm mt-3 btn-outline-primary&quot;&gt;@MultiTenancyStringLocalizer[&quot;Switch&quot;]&lt;/a&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                }
                @(await Component.InvokeAsync&lt;PageAlertsViewComponent&gt;())
                @await Component.InvokeLayoutHookAsync(LayoutHooks.PageContent.First, StandardLayouts.Account)
                @RenderBody()
                @await Component.InvokeLayoutHookAsync(LayoutHooks.PageContent.Last, StandardLayouts.Account)
            &lt;/abp-column&gt;
        &lt;/abp-row&gt;
    &lt;/div&gt;

    &lt;abp-script-bundle name=&quot;@BasicThemeBundles.Scripts.Global&quot; /&gt;

    &lt;script src=&quot;~/Abp/ApplicationConfigurationScript&quot;&gt;&lt;/script&gt;
    &lt;script src=&quot;~/Abp/ServiceProxyScript&quot;&gt;&lt;/script&gt;

    @await RenderSectionAsync(&quot;scripts&quot;, false)

    @await Component.InvokeAsync(typeof(WidgetScriptsViewComponent))

    @await Component.InvokeLayoutHookAsync(LayoutHooks.Body.Last, StandardLayouts.Account)
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>In this file, we removed the <code>bg-light</code> class from the <code>body</code> element.</p>
<p><strong>Themes/Basic/Layouts/Application.cshtml</strong>:</p>
<pre><code class="language-cs">@using Volo.Abp.AspNetCore.Mvc.UI.Components.LayoutHook
@using Volo.Abp.AspNetCore.Mvc.UI.Layout
@using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Bundling
@using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Themes.Basic.Components.MainNavbar
@using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Themes.Basic.Components.PageAlerts
@using Volo.Abp.AspNetCore.Mvc.UI.Theming
@using Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetScripts
@using Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetStyles
@using Volo.Abp.Localization
@using Volo.Abp.Ui.Branding
@inject IBrandingProvider BrandingProvider
@inject IPageLayout PageLayout
@{
    Layout = null;
    var containerClass = ViewBag.FluidLayout == true ? &quot;container-fluid&quot; : &quot;container&quot;; //TODO: Better and type-safe options

    var pageTitle = ViewBag.Title == null ? BrandingProvider.AppName : ViewBag.Title; //TODO: Discard to get from Title

    if (PageLayout.Content.Title != null)
    {
        if (!string.IsNullOrWhiteSpace(pageTitle))
        {
            pageTitle = &quot; | &quot; + pageTitle;
        }

        pageTitle = PageLayout.Content.Title + pageTitle;
    }

    var rtl = CultureHelper.IsRtl ? &quot;rtl&quot; : string.Empty;
}

&lt;!DOCTYPE html&gt;

&lt;html lang=&quot;@CultureInfo.CurrentCulture.Name&quot; dir=&quot;@rtl&quot;&gt;
&lt;head&gt;
    @await Component.InvokeLayoutHookAsync(LayoutHooks.Head.First, StandardLayouts.Application)

    &lt;meta charset=&quot;utf-8&quot;&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1, shrink-to-fit=no&quot;&gt;
    &lt;meta http-equiv=&quot;X-UA-Compatible&quot; content=&quot;ie=edge&quot;&gt;

    &lt;title&gt;@pageTitle&lt;/title&gt;

    &lt;abp-style-bundle name=&quot;@BasicThemeBundles.Styles.Global&quot; /&gt;

    @await Component.InvokeAsync(typeof(WidgetStylesViewComponent))

    @await RenderSectionAsync(&quot;styles&quot;, false)

    @await Component.InvokeLayoutHookAsync(LayoutHooks.Head.Last, StandardLayouts.Application)
&lt;/head&gt;
&lt;body class=&quot;abp-application-layout @rtl&quot;&gt;
    @await Component.InvokeLayoutHookAsync(LayoutHooks.Body.First, StandardLayouts.Application)

    @(await Component.InvokeAsync&lt;MainNavbarViewComponent&gt;())

    &lt;div class=&quot;@containerClass&quot;&gt;
        @(await Component.InvokeAsync&lt;PageAlertsViewComponent&gt;())
        &lt;div id=&quot;AbpContentToolbar&quot;&gt;
            &lt;div class=&quot;text-end mb-2&quot;&gt;
                @RenderSection(&quot;content_toolbar&quot;, false)
            &lt;/div&gt;
        &lt;/div&gt;
        @await Component.InvokeLayoutHookAsync(LayoutHooks.PageContent.First, StandardLayouts.Application)
        @RenderBody()
        @await Component.InvokeLayoutHookAsync(LayoutHooks.PageContent.Last, StandardLayouts.Application)
    &lt;/div&gt;

    &lt;abp-script-bundle name=&quot;@BasicThemeBundles.Scripts.Global&quot; /&gt;

    &lt;script src=&quot;~/Abp/ApplicationConfigurationScript&quot;&gt;&lt;/script&gt;
    &lt;script src=&quot;~/Abp/ServiceProxyScript&quot;&gt;&lt;/script&gt;

    @await Component.InvokeAsync(typeof(WidgetScriptsViewComponent))

    @await RenderSectionAsync(&quot;scripts&quot;, false)

    @await Component.InvokeLayoutHookAsync(LayoutHooks.Body.Last, StandardLayouts.Application)
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>In this file, we removed the <code>bg-light</code> class from the <code>body</code> element.</p>
<p><strong>Themes/Basic/_ViewImports.cshtml</strong>:</p>
<pre><code class="language-cs">@using System.Globalization
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling
</code></pre>
<p>This file is left unchanged and is just needed for imports used in the other files.</p>
<p>Now that this is done, if we run the <strong>BootswatchDemo.Web</strong> project again, the app should look correct.</p>
<p>If you open the app in your browser, you should get something like this:</p>
<p><img src="https://raw.githubusercontent.com/jonathanpotts/AbpBootswatchDemo/main/BootswatchDemo-DarklyFixed.png" alt="Overriden ABP Basic Theme with Darkly Bootswatch Theme" /></p>
<p>Now our app looks just like how the Darkly theme appears on the Bootswatch gallery.</p>
<p>Let's stop the app for now.</p>
<h2>Bonus: Adding Theme Switching</h2>
<p>Since Bootswatch comes with so many themes, it might be nice if we could allow our users to choose which one they like the best.</p>
<p>We can add this functionality by adding a cookie that stores which theme the user has selected and use it to load a specific Bootswatch theme.</p>
<p>In the <strong>BootswatchDemo.Web</strong> project directory, create a file called <strong>BootswatchDemoWebConsts.cs</strong>. The contents of this file should be:</p>
<pre><code class="language-cs">namespace BootswatchDemo.Web
{
    public static class BootswatchDemoWebConsts
    {
        public const string ThemeCookie = &quot;theme&quot;;
        public const string DefaultTheme = &quot;darkly&quot;;
    }
}
</code></pre>
<p>This file defines the name of the cookie we will use to store the user's selected theme as well as the default theme to use if the user has not made a selection.</p>
<p>Now, let's modify the <strong>Bundling/BootswatchStyleContributor.cs</strong> file that we created earlier in the <strong>BootswatchDemo.Web</strong> project directory.</p>
<p>Add the following using statements at the top of the file:</p>
<pre><code class="language-cs">using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
</code></pre>
<p>Replace:</p>
<pre><code class="language-cs">            var theme = &quot;darkly&quot;;
</code></pre>
<p>With:</p>
<pre><code class="language-cs">            var httpContext = context.ServiceProvider.GetService&lt;IHttpContextAccessor&gt;().HttpContext;
            
            if (!httpContext.Request.Cookies.TryGetValue(BootswatchDemoWebConsts.ThemeCookie, out var theme))
            {
                theme = BootswatchDemoWebConsts.DefaultTheme;
            }
</code></pre>
<p>This will have the <strong>BootswatchStyleContributor</strong> change the theme if the cookie is set.</p>
<p>We also need to provide a way for the user to change the theme. Let's create a Razor Page to handle setting the theme cookie.</p>
<p>Create a file in the <strong>Pages</strong> directory under the <strong>BootswatchDemo.Web</strong> project directory called <strong>Theme.cshtml</strong> with the following contents:</p>
<pre><code class="language-cs">@page
@model BootswatchDemo.Web.Pages.ThemeModel
@{
}
</code></pre>
<p>Create another file in the same directory called <strong>Theme.cshtml.cs</strong> with the following contents:</p>
<pre><code class="language-cs">using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System;

namespace BootswatchDemo.Web.Pages
{
    public class ThemeModel : PageModel
    {
        public void OnGet(string theme, string returnUrl = null)
        {
            var options = new CookieOptions()
            {
                Expires = DateTime.UtcNow.AddYears(10)
            };

            HttpContext.Response.Cookies.Append(BootswatchDemoWebConsts.ThemeCookie, theme, options);
            
            HttpContext.Response.Redirect(returnUrl ?? Url.Content(&quot;~/&quot;));
        }
    }
}
</code></pre>
<p>With these files, we can navigate a user to <code>~/Theme</code> with the selected theme provided as <code>theme</code> in the query string and it will set the cookie. It will then either redirect them to the front page, or a specific URL if there is a value for <code>returnUrl</code> provided in the query string.</p>
<p>Now, we need to setup a menu to allow the user to select the theme they want.</p>
<p>Open the <strong>Menus/BootswatchDemoMenuContributor.cs</strong> file under the <strong>BootswatchDemo.Web</strong> project directory.</p>
<p>Add the following using statements to the top of the file:</p>
<pre><code class="language-cs">using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
</code></pre>
<p>Replace the <code>ConfigureMainMenuAsync(MenuConfigurationContext)</code> method with:</p>
<pre><code class="language-cs">    private async Task ConfigureMainMenuAsync(MenuConfigurationContext context)
    {
        var administration = context.Menu.GetAdministration();
        var l = context.GetLocalizer&lt;BootswatchDemoResource&gt;();

        context.Menu.Items.Insert(
            0,
            new ApplicationMenuItem(
                BootswatchDemoMenus.Home,
                l[&quot;Menu:Home&quot;],
                &quot;~/&quot;,
                icon: &quot;fas fa-home&quot;,
                order: 0
            )
        );

        var themesMenu = new ApplicationMenuItem(
            &quot;Themes&quot;,
            l[&quot;Menu:Themes&quot;],
            icon: &quot;fas fa-paint-brush&quot;);

        (string, LocalizedString)[] themes = new[]
        {
            ( &quot;darkly&quot;, l[&quot;Menu:Themes:Darkly&quot;] ),
            ( &quot;flatly&quot;, l[&quot;Menu:Themes:Flatly&quot;] ),
            ( &quot;vapor&quot;, l[&quot;Menu:Themes:Vapor&quot;] )
        };

        var httpContext = context.ServiceProvider.GetService&lt;IHttpContextAccessor&gt;().HttpContext;
        
        if (!httpContext.Request.Cookies.TryGetValue(BootswatchDemoWebConsts.ThemeCookie, out var theme))
        {
            theme = BootswatchDemoWebConsts.DefaultTheme;
        }

        foreach (var (name, displayName) in themes)
        {
            themesMenu.AddItem(new ApplicationMenuItem(
                name,
                displayName,
                url: $&quot;~/Theme?theme={name}&amp;returnUrl={httpContext.Request.Path}&quot;,
                cssClass: theme == name ? &quot;active&quot; : null));
        }

        context.Menu.Items.Add(themesMenu);

        if (MultiTenancyConsts.IsEnabled)
        {
            administration.SetSubItemOrder(TenantManagementMenuNames.GroupName, 1);
        }
        else
        {
            administration.TryRemoveMenuItem(TenantManagementMenuNames.GroupName);
        }

        administration.SetSubItemOrder(IdentityMenuNames.GroupName, 2);
        administration.SetSubItemOrder(SettingManagementMenuNames.GroupName, 3);
    }
</code></pre>
<p>This adds a menu for setting the Theme with three theme options (Darkly, Flatly, and Vapor). You can modify the <code>themes</code> variable to easily add, change, or remove the themes in the menu.</p>
<p>Since we added localized strings to the menu, we need to set them up. Under the <strong>BootswatchDemo.Domain.Shared</strong> project directory, open the <strong>Localization/BootswatchDemo/en.json</strong> file. Add the following to the end of the <code>&quot;texts&quot;</code> object:</p>
<pre><code class="language-json">    &quot;Menu:Themes&quot;: &quot;Themes&quot;,
    &quot;Menu:Themes:Darkly&quot;: &quot;Darkly&quot;,
    &quot;Menu:Themes:Flatly&quot;: &quot;Flatly&quot;,
    &quot;Menu:Themes:Vapor&quot;: &quot;Vapor&quot;
</code></pre>
<p>Finally, run the <strong>BootswatchDemo.Web</strong> project again and we should see the menu for selecting a theme.</p>
<p>If you open the app in your browser, you should get something like this:</p>
<p><img src="https://raw.githubusercontent.com/jonathanpotts/AbpBootswatchDemo/main/BootswatchDemo-ThemeMenu.png" alt="Theme Selection Menu Added" /></p>
<p>And if you select the Vapor theme from the Themes menu, you should get something like this:</p>
<p><img src="https://raw.githubusercontent.com/jonathanpotts/AbpBootswatchDemo/main/BootswatchDemo-Vapor.png" alt="Overridden ABP Basic Theme with Vapor Bootswatch Theme" /></p>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/api/posts/cover-picture-source/4619f46f-469e-cfe8-1e52-3a01b4f13a37" />
      <media:content url="https://abp.io/api/posts/cover-picture-source/4619f46f-469e-cfe8-1e52-3a01b4f13a37" medium="image" />
    </item>
  </channel>
</rss>