Change Telerik Theme at Runtime
Environment
Product | UI for Blazor |
Description
This KB article answers the following questions:
- How to change the Telerik CSS theme or swatch at runtime?
- How to allow users to switch the component theme on the fly?
- How to toggle light and dark mode for the Telerik UI for Blazor components?
Solution
The app stylesheets reside outside the Razor component hierarchy, so the Blazor code cannot access them. You can change the current Telerik theme or swatch at runtime with JSInterop.
The following algorithm follows the commonly used approach to replace a CSS file in any web app. You can use it for built-in themes and custom themes, regardless of the CSS theme's physical location.
-
Register the initial Telerik theme in a way that allows you to get reference to the
<link>
tag. For example, use anid
attribute.<link id="telerik-theme" rel="stylesheet" href="https://unpkg.com/@progress/kendo-theme-default@10.0.1/dist/default-main.css" />
-
Implement a JavaScript function that creates a
<link>
element with the new theme and appends it to the page. Remove the old<link>
element when the new one loads. Use the chosenid
value with both<link>
tags. Notify the Razor component when the new CSS file is loaded, if you need to make additional changes to the UI.var themeChangerDotNetRef; function saveDotNetRef(dotNetRef) { themeChangerDotNetRef = dotNetRef; } function changeTelerikTheme(newUrl) { var oldLink = document.getElementById("telerik-theme"); if (newUrl === oldLink.getAttribute("href")) { return; } var newLink = document.createElement("link"); newLink.setAttribute("id", "telerik-theme"); newLink.setAttribute("rel", "stylesheet"); newLink.setAttribute("href", newUrl); newLink.onload = () => { oldLink.parentElement.removeChild(oldLink); themeChangerDotNetRef.invokeMethodAsync("NotifyThemeChanged"); }; document.getElementsByTagName("head")[0].appendChild(newLink); }
-
Implement UI that triggers the JavaScript theme change. After the new CSS theme is loaded, refresh all Telerik components that use SVG or Canvas rendering, such as BarCodes, Charts, Gauges, and QR Codes.
Make sure the version number in the theme URL is compatible with the version of Telerik UI for Blazor.
Replace
Index
in the code below with the correct Razor component name.@implements IDisposable @inject IJSRuntime js <div class="k-body" style="padding:2em;"> <label> Select a Telerik theme: <TelerikDropDownList Data="@ThemeData" Value="@ThemeSwatchValue" ValueChanged="@ThemeSwatchValueChanged" TItem="@ThemeModel" TValue="@int" ValueField="@nameof(ThemeModel.Id)" TextField="@nameof(ThemeModel.FullName)" Width="240px"> </TelerikDropDownList> </label> <TelerikLoader Visible="@LoaderVisible" /> <br /><br /> <TelerikGrid Data="@GridData" TItem="@GridModel" Pageable="true" PageSize="3" Sortable="true" FilterMode="GridFilterMode.FilterRow"> <GridColumns> <GridColumn Field="@nameof(GridModel.Name)" /> <GridColumn Field="@nameof(GridModel.Price)" /> <GridColumn Field="@nameof(GridModel.Quantity)" /> </GridColumns> </TelerikGrid> <TelerikChart @ref="@ChartRef" Height="240px"> <ChartSeriesItems> <ChartSeries Type="ChartSeriesType.Line" Data="@Series1Data" Field="@nameof(ChartModel.Revenue)" CategoryField="@nameof(ChartModel.TimePeriod)" Name="Product 1"> </ChartSeries> <ChartSeries Type="ChartSeriesType.Column" Data="@Series2Data" Field="@nameof(ChartModel.Revenue)" CategoryField="@nameof(ChartModel.TimePeriod)" Name="Product 2"> </ChartSeries> <ChartSeries Type="ChartSeriesType.Area" Data="@Series3Data" Field="@nameof(ChartModel.Revenue)" CategoryField="@nameof(ChartModel.TimePeriod)" Name="Product 3"> </ChartSeries> </ChartSeriesItems> <ChartCategoryAxes> <ChartCategoryAxis Type="@ChartCategoryAxisType.Date"></ChartCategoryAxis> </ChartCategoryAxes> <ChartValueAxes> <ChartValueAxis Max="600"> <ChartValueAxisLabels Format="c2" /> </ChartValueAxis> </ChartValueAxes> <ChartTitle Text="Telerik Chart"></ChartTitle> <ChartLegend Position="ChartLegendPosition.Right"> </ChartLegend> </TelerikChart> </div> @code { // Replace "Index" with the name of the Razor component that holds this code private DotNetObjectReference<Index>? DotNetRef { get; set; } private TelerikChart? ChartRef { get; set; } private List<GridModel> GridData { get; set; } = new(); private List<ChartModel> Series1Data { get; set; } = new(); private List<ChartModel> Series2Data { get; set; } = new(); private List<ChartModel> Series3Data { get; set; } = new(); private List<ThemeModel> ThemeData { get; set; } = new(); private int ThemeSwatchValue { get; set; } = 1; private const string ThemeUrlTemplate = "https://unpkg.com/@progress/kendo-theme-{0}@10.0.1/dist/{0}-{1}.css"; private bool LoaderVisible { get; set; } private async Task ThemeSwatchValueChanged(int newValue) { // Update DropDownList Value ThemeSwatchValue = newValue; LoaderVisible = true; // Generate new theme URL ThemeModel newThemeModel = ThemeData.First(x => x.Id == ThemeSwatchValue); string newThemeSwatchUrl = string.Format(ThemeUrlTemplate, newThemeModel.Theme.ToLower(), newThemeModel.Swatch.ToLower()); // Change current Telerik theme await js.InvokeVoidAsync("changeTelerikTheme", newThemeSwatchUrl); // The algorithm continues in the NotifyThemeChanged method } [JSInvokable("NotifyThemeChanged")] public void NotifyThemeChanged() { // Refresh all Telerik components that use SVG or Canvas rendering (Charts, Gauges, BarCodes, QR Codes) ChartRef?.Refresh(); LoaderVisible = false; // This method is not an EventCallback, so you need StateHasChanged() to hide the Loader or make other changes in the UI StateHasChanged(); } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { // Ensure HTML is ready await Task.Delay(1); // Send the Razor component's reference to the client // to be able to call NotifyThemeChanged() await js.InvokeVoidAsync("saveDotNetRef", DotNetRef); } await base.OnAfterRenderAsync(firstRender); } protected override async Task OnInitializedAsync() { DotNetRef = DotNetObjectReference.Create(this); PopulateThemes(); GenerateData(); await base.OnInitializedAsync(); } private void PopulateThemes() { ThemeData.Add(new ThemeModel(1, "Default", "Main")); ThemeData.Add(new ThemeModel(2, "Default", "Main-Dark")); ThemeData.Add(new ThemeModel(3, "Default", "Ocean-Blue")); ThemeData.Add(new ThemeModel(4, "Bootstrap", "Main")); ThemeData.Add(new ThemeModel(5, "Bootstrap", "Main-Dark")); ThemeData.Add(new ThemeModel(6, "Material", "Main")); ThemeData.Add(new ThemeModel(7, "Material", "Main-Dark")); ThemeData.Add(new ThemeModel(8, "Fluent", "Main")); } private void GenerateData() { for (int i = 1; i <= 17; i++) { GridData.Add(new GridModel() { Id = i, Name = $"Name {i}", Price = Random.Shared.Next(1, 100) * 1.23m, Quantity = Random.Shared.Next(0, 1000) }); } var now = DateTime.Today; var monthsBack = 6; for (int i = 1; i <= monthsBack; i++) { var dateTimeValue = now.AddMonths(-monthsBack + i); Series1Data.Add(new ChartModel() { Id = i, Product = "Product 1", Revenue = Random.Shared.Next(1, 500), TimePeriod = dateTimeValue }); Series2Data.Add(new ChartModel() { Id = i, Product = "Product 2", Revenue = Random.Shared.Next(1, 500), TimePeriod = dateTimeValue }); Series3Data.Add(new ChartModel() { Id = i, Product = "Product 3", Revenue = Random.Shared.Next(1, 500), TimePeriod = dateTimeValue }); } } public void Dispose() { DotNetRef?.Dispose(); } public class ThemeModel { public int Id { get; set; } public string Theme { get; set; } public string Swatch { get; set; } public string FullName => $"{Theme} {Swatch}"; public ThemeModel(int id, string themeName, string swatchName) { Id = id; Theme = themeName; Swatch = swatchName; } } public class GridModel { public int Id { get; set; } public string Name { get; set; } = string.Empty; public decimal Price { get; set; } public int Quantity { get; set; } } public class ChartModel { public int Id { get; set; } public string Product { get; set; } = string.Empty; public DateTime TimePeriod { get; set; } public decimal Revenue { get; set; } } }