Title: Add per-column value converters (WPF-style) to Telerik Blazor Grid columns
Product: UI for Blazor → Grid
Type: Feature Request
Description:
In Telerik UI for WPF, grid column definitions can reference a converter by name (e.g., IValueConverter) to transform values for display without writing a custom cell template for each column.
In Telerik UI for Blazor Grid, templates are currently the primary way to change how values render in a column (which works, but becomes repetitive across many columns/grids).
Request:
Add a column-level conversion API that allows specifying a converter/formatter function for display (and optionally editing). Example concepts:
Converter="nameof(MyConverters.StatusToText)"
or DisplayConverter="(item) => …" / ValueFormatter="Func<TItem, object, string>"
optionally ConvertBack-like support for editing scenarios (or separate EditConverter)
Why this is needed:
Reduces repeated <Template> markup for simple formatting/transformations
Centralizes formatting/conversion logic in reusable code
Improves maintainability and consistency across grids
Eases migration for teams coming from Telerik WPF where converters are a common pattern
Use cases:
Enum/int → user-friendly text
Boolean → “Yes/No”, icons, badges
Null/empty → placeholder text
Code → display name (via lookup)
Domain-specific formatting shared across many grids
Workarounds today:
Column <Template> or computed properties (both valid, but not as concise/reusable for large grids)
Extracting RenderFragments can reduce repetition, but still requires templating infrastructure for what is logically a simple conversion step
Expected behavior:
Converter applies consistently anywhere the column renders (cell, export if applicable, etc.)
Works with sorting/filtering in a predictable way (ideally sorting/filtering still uses raw field value unless explicitly configured)
On a grid grouped by one or more fields, provide an option for end users to expand/collapse all rows at will. The following image is a suggestion of where this option (most likely a button) should appear:
We use QueryableExtensions.ToDataSourceResultmethod to load some data in our component. And at some moment we need to cancel data loading. But ToDataSourceResult method doesn’t support CancellationToken. So we are forced to use a workaround and just ignore the task's result. But task is still executing and causing the performance hit…
It would be great if you implemented support for this feature!
Hello,
at version 12.3.0 TelerikFilter is crashing "at load". Prior this version, same markup = ok.
Error: System.InvalidOperationException: Object of type 'Telerik.Blazor.Components.TelerikFilter' does not have a property matching the name 'ValueChanged'.
also in demo:
https://www.telerik.com/blazor-ui/documentation/components/filter/integration
usage:
<TelerikFilter @bind-Value="@AdvancedFilterValue">
<FilterFields>
@foreach (var it in GridDef.ColStore.Where(x => x.Verejny == true))
{
<FilterField Name="@it.FldName" Type="@it.FldType" Label="@it.VerejnyNazev"></FilterField>
}
</FilterFields>
</TelerikFilter>The TelerikPanelBar component is using Right and Left arrows on keyboard to opening and closing each tab.
https://demos.telerik.com/blazor-ui/panelbar/keyboard-navigation
When the page is zoomed to 200%, it gets a horizontal scrollbar, which is normally controlled with the left and right arrow keys on keyboard. However, these keys are assigned to opening and closing the TelerikPanelBar component, which makes it impossible to scroll the page horizontally while we have Tab on TelerikPanelBar. Is there any workaround that would allow horizontal scrolling with the left and right arrow keys on keyboard at this zoom level? Maybe it’s possible to change TelerikPanelBar behavior and opening and closing it by different keys?
For example in DropDownList reflection is used for binding for TextField and ValueField.
As an additional option, I'd instead to be able to do DropDownList<FooBarModel> and then FooBarModel implements a Telerik interface IDropDownListItem (for example but use your naming conventions)
I agree you shouldn't remove the option of using TextField / ValueField but I'd like to see it as an option.
Doing the same thing in something like DataGrid I agree would be a ton more work and we'd likely require using source generation.
It seems that Microsoft / the industry is consistently recommending to phase out the use of reflection and have invested a bunch in making source generation much less of a pain and developer friendly.
In our application we use some large datasets and present them in a TelerikGrid. We use WPF + Blazor Hybrid and noticed, that in some cases the memory usage of the Web View process grows up to some gigabytes.
Here a screenshot of the task manager with a lot of RAM usage for the web view.
Here a screenshot of the detached DOM elements after a two navigations. The container divs are not garbage collected.
I tracked down the issue to come from the TelerikGrid, because when I remove it from the pages, everything runs fine. I also removed all GridColumns and the issue is still present. In the developer tools I noticed that one of the parent div elements remains in memory every time I navigate back and forth.
I also created a blank Blazor WebAssembly Standalone application and added a simple instance of the grid. Here, the issue is also present. I attach the one blazor page that causes the issue.
I've tested all major versions from 5.1 upwards, every version is affected.
After upgrading to 12.0.0, the Content does not change when clicking tabs. I always see the Content of the first tab.
My project targets .net8.
A Blazor component designed to provide an interactive image magnification experience, similar to popular eCommerce websites.
MagnifyScale parameter.@inject IJSRuntime JS
@using Microsoft.JSInterop
@* Container *@
<div @ref="_containerRef" class="@($"magnifiable-image-container {Class}")" style="height: @Height; width: @Width;">
@* Image *@
<button @onclick="@OnClick" class="magnifiable-image-button">
<img src="@Image.Src" alt="@Image.Alt" style="height: 100%; width: 100%;"
@onmousemove="@OnMouseMove" @onmouseenter="@OnMouseEnterAsync" @onmouseleave="@OnMouseLeave"/>
</button>
@* Magnifier *@
<TelerikPopover @ref="_popoverRef" AnchorSelector=".magnifiable-image-container" Position="@(_showOnRight ? PopoverPosition.Right : PopoverPosition.Left)" Offset="@MagnifierMargin"
Width="@($"{_magnifierWidth}px")" Height="@($"{_magnifierHeight}px")" Class="popover-magnifier" Collision="PopoverCollision.Fit">
<PopoverContent>
@* Magnified Image *@
<img src="@Image.Src" alt="@Image.Alt" class="magnified-image"
style="@($"width: {_magnifiedImageWidth}px; height: {_magnifiedImageHeight}px; transform: translateX({_magnifiedImageTransformX}px) translateY({_magnifiedImageTransformY}px); left: {_magnifiedImageLeft}px; top: {_magnifiedImageTop}px;")"/>
</PopoverContent>
</TelerikPopover>
@* Magnifier Overlay *@
@if (_isMouseOver)
{
<div class="magnifier-overlay"
style="@($"width: {_magnifierOverlayWidth}px; height: {_magnifierOverlayHeight}px; transform: translateX({_magnifierOverlayTransformX}px) translateY({_magnifierOverlayTransformY}px); left: {_magnifierOverlayLeft}px; top: {_magnifierOverlayTop}px;")))">
</div>
}
@* Actual Image *@
<TelerikWindow @bind-Visible="@_isClicked" Modal="true" CloseOnOverlayClick="true" Draggable="false" Resizable="false" Class="window-rounded">
<WindowActions>
<WindowAction Name="Close"/>
</WindowActions>
<WindowContent>
<img src="@Image.Src" alt="@Image.Alt"/>
</WindowContent>
</TelerikWindow>
</div>
<style>
.magnifiable-image-container {
position: relative;
display: inline-block;
cursor: zoom-in;
}
.magnifiable-image-button {
background: none;
border: none;
padding: 0;
margin: 0;
font: inherit;
color: inherit;
cursor: inherit !important;
outline: none;
box-shadow: none;
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
display: block;
width: 100%;
height: 100%;
}
.magnifiable-image-button:focus-visible {
outline: none;
box-shadow: 0 0 0 2px color-mix(in srgb, var(--kendo-color-on-app-surface, #424242) 50%, transparent);
}
.popover-magnifier {
overflow: hidden;
border-radius: 0;
position: relative;
top: @(_adjustForTelerikFit ? $"{(_showOnBottom ? "" : "-")}{MagnifierMargin/4}px" : $"{(_showOnBottom ? "" : "-")}{MagnifierMargin}px");
}
.magnified-image {
position: absolute;
}
.magnifier-overlay {
position: absolute;
background: color-mix(in srgb, var(--kendo-color-primary, #1274AC) 15%, transparent);
pointer-events: none;
z-index: 5;
box-sizing: border-box;
display: block;
}
</style>
@code
{
[Parameter] public required ImageInfo Image { get; set; }
/// <summary>
/// The height of the image (e.g., "200px", "100%", or "auto"). Default is "auto".
/// </summary>
[Parameter]
public string Height { get; set; } = "auto";
/// <summary>
/// The width of the image (e.g., "200px", "100%", or "auto"). Default is "auto".
/// </summary>
[Parameter]
public string Width { get; set; } = "auto";
/// <summary>
/// The magnification scale for the magnifier. Default is 3 (3x magnification).
/// </summary>
[Parameter]
public double MagnifyScale { get; set; } = 3;
/// <summary>
/// Applies additional CSS classes to the MagnifiableImage's root element for custom styling and visual modifications.
/// </summary>
[Parameter]
public string Class { get; set; } = string.Empty;
private const int MagnifierMargin = 24;
// State for magnifier visibility and container reference
private bool _isMouseOver;
private bool _isClicked;
private ElementReference _containerRef;
private TelerikPopover? _popoverRef;
// Image and magnified image dimensions
private double _imageWidth;
private double _imageHeight;
private double _magnifiedImageWidth;
private double _magnifiedImageHeight;
// Magnifier position and size
private bool _showOnRight = true;
private bool _showOnBottom = true;
private bool _adjustForTelerikFit;
private double _magnifierWidth;
private double _magnifierHeight;
// Magnified image offset within the magnifier
private double _magnifiedImageLeft;
private double _magnifiedImageTop;
// Mouse position clamping bounds
private double _minMouseX;
private double _minMouseY;
private double _maxMouseX;
private double _maxMouseY;
// Mouse position and transform for magnified image
private double _mouseX;
private double _mouseY;
private double _magnifiedImageTransformX;
private double _magnifiedImageTransformY;
// Magnifier overlay size and transform
private double _magnifierOverlayWidth;
private double _magnifierOverlayHeight;
private double _magnifierOverlayLeft;
private double _magnifierOverlayTop;
private double _magnifierOverlayTransformX;
private double _magnifierOverlayTransformY;
private void OnClick()
{
_isClicked = true;
}
private async Task OnMouseEnterAsync()
{
_isMouseOver = true;
_popoverRef?.Show();
// Get layout info about the image container using getElementLayoutInfo from wwwroot/js/magnifiable-image.js
var containerLayoutInfo = await JS.InvokeAsync<ElementLayoutInfo>("getElementLayoutInfo", _containerRef);
// Store image size
_imageWidth = containerLayoutInfo.Width;
_imageHeight = containerLayoutInfo.Height;
_magnifiedImageWidth = _imageWidth * MagnifyScale;
_magnifiedImageHeight = _imageHeight * MagnifyScale;
// Determine magnifier position based on available space
_showOnRight = containerLayoutInfo.DistanceFromViewportRight >= containerLayoutInfo.DistanceFromViewportLeft;
_adjustForTelerikFit = Math.Abs(containerLayoutInfo.DistanceFromViewportBottom - containerLayoutInfo.DistanceFromViewportTop) < 20;
_showOnBottom = containerLayoutInfo.DistanceFromViewportBottom >= containerLayoutInfo.DistanceFromViewportTop;
// Calculate magnifier size based on available space
_magnifierWidth = _showOnRight
? containerLayoutInfo.DistanceFromViewportRight - MagnifierMargin*2
: containerLayoutInfo.DistanceFromViewportLeft - MagnifierMargin*2;
_magnifierHeight = containerLayoutInfo.ViewportHeight - MagnifierMargin*2;
// Center the magnified image in the magnifier
_magnifiedImageLeft = (_magnifierWidth / 2) - (_imageWidth / 2);
_magnifiedImageTop = (_magnifierHeight / 2) - (_imageHeight / 2);
// Calculate min and max mouse X/Y to prevent showing empty space in the magnifier
_minMouseX = Math.Floor((_magnifierWidth / MagnifyScale) / 2);
_minMouseY = Math.Floor((_magnifierHeight / MagnifyScale) / 2);
_maxMouseX = Math.Ceiling(_imageWidth - ((_magnifierWidth / MagnifyScale) / 2));
_maxMouseY = Math.Ceiling(_imageHeight - ((_magnifierHeight / MagnifyScale) / 2));
// Calculate magnifier overlay size and position
_magnifierOverlayWidth = Math.Floor(Math.Clamp(_magnifierWidth / MagnifyScale, 0, _imageWidth)) - 1;
_magnifierOverlayHeight = Math.Floor(Math.Clamp(_magnifierHeight / MagnifyScale, 0, _imageHeight)) - 1;
_magnifierOverlayLeft = -((_magnifierOverlayWidth / 2) + 1);
_magnifierOverlayTop = -((_magnifierOverlayHeight / 2) + 1);
}
private void OnMouseLeave()
{
_isMouseOver = false;
_popoverRef?.Hide();
}
private void OnMouseMove(MouseEventArgs e)
{
// Clamp mouse X/Y to prevent showing empty space in the magnifier
if (_minMouseX > _maxMouseX) _mouseX = _imageWidth / 2;
else _mouseX = Math.Clamp(e.OffsetX, _minMouseX, _maxMouseX);
if (_minMouseY > _maxMouseY) _mouseY = _imageHeight / 2;
else _mouseY = Math.Clamp(e.OffsetY, _minMouseY, _maxMouseY);
// Calculate the transform for the magnified image
_magnifiedImageTransformX = -Math.Round((_mouseX * MagnifyScale) - (_imageWidth / 2));
_magnifiedImageTransformY = -Math.Round((_mouseY * MagnifyScale) - (_imageHeight / 2));
// Calculate the transform for the magnifier overlay
_magnifierOverlayTransformX = Math.Round(_mouseX);
_magnifierOverlayTransformY = Math.Round(_mouseY);
_popoverRef?.Refresh();
}
private record ElementLayoutInfo(
double Width,
double Height,
double ViewportHeight,
double DistanceFromViewportLeft, // distance from viewport's left edge to element's left edge
double DistanceFromViewportRight, // distance from element's right edge to viewport's right edge
double DistanceFromViewportTop, // distance from viewport's top edge to element's top edge
double DistanceFromViewportBottom); // distance from element's bottom edge to viewport's bottom edge
}// Returns width, height, and the space to the left and right of the element relative to the viewport
window.getElementLayoutInfo = (element) => {
if (!element) return null;
const elementRect = element.getBoundingClientRect();
return {
width: elementRect.width,
height: elementRect.height,
viewportHeight: window.innerHeight,
distanceFromViewportLeft: elementRect.left, // distance from viewport's left edge to element's left edge
distanceFromViewportRight: window.innerWidth - elementRect.right, // distance from element's right edge to viewport's right edge
distanceFromViewportTop: elementRect.top, // distance from viewport's top edge to element's top edge
distanceFromViewportBottom: window.innerHeight - elementRect.bottom // distance from element's bottom edge to viewport's bottom edge
};
};Only tested in Blazor WebAssembly. The component may see performance issues in Blazor Server.
A flexible and accessible image viewer / selector for Blazor applications, similar to what popular eCommerce websites use to show products.
Height and Width parameters to fit various UI needs.ConstrainImageHeight and ConstrainImageWidth parameters allow precise control over aspect ratio, whether to maintain aspect ratio and/or constrain height and width.ImageInfo objects, each with a required image source and optional alt text for accessibility.@inject ITelerikStringLocalizer Loc
<LanguageTrackProvider OnInitializeEvent="provider => provider.RegisterComponent(this)" />
@if (Images.Any() && _selectedImage != null)
{
<div class="d-flex @Class" style="height: @Height; width: @Width;">
<ul aria-label="@Loc["ImageThumbnails"]" role="radiogroup" class="image-collection-button-container">
@foreach (var image in Images)
{
<li>
<button @onclick="() => OnImageSelect(image)"
class="@($"image-collection-button{(image.Src == _selectedImage.Src ? " selected" : "")}")"
role="radio" aria-checked="@(image.Src == _selectedImage.Src ? "true" : "false")">
<img src="@image.Src" alt="@image.Alt" />
</button>
</li>
}
</ul>
<div class="image-collection-image-container">
<MagnifiableImage Image="@(new ImageInfo(_selectedImage.Src, _selectedImage.Alt))" Class="image-collection-image" MagnifyScale="@MagnifyScale"
Height="@(ConstrainImageHeight ? "calc(100% - 2px)" : "auto")" Width="@(ConstrainImageWidth ? "100%" : "auto")" />
</div>
</div>
}
else
{
<TelerikSkeleton ShapeType="@SkeletonShapeType.Rectangle" Height="@Height" Width="@Width" Class="@Class"/>
}
<style>
.image-collection-button-container {
flex: 0 0 auto;
width: 10%;
height: 100%;
min-width: 90px;
max-width: 110px;
overflow-y: auto;
padding: 2px;
margin: 0;
scrollbar-color: rgba(1, 1, 1, 0.25) rgba(0, 0, 0, 0);
scrollbar-gutter: stable;
list-style: none;
}
.image-collection-button {
padding: 0;
margin-bottom: 1rem;
border: 1px solid var(--kendo-color-border, rgba(0, 0, 0, 0.08));
border-radius: 0.5rem;
width: auto;
aspect-ratio: 1 / 1;
display: block;
}
.image-collection-button:hover {
filter: brightness(90%);
}
.image-collection-button:focus {
outline: none;
box-shadow: 0 0 0 2px color-mix(in srgb, var(--kendo-color-on-app-surface, #424242) 50%, transparent);
}
.image-collection-button.selected {
box-shadow: 0 0 0 2px var(--kendo-color-primary, #1274AC);
}
.image-collection-button img {
width: 100%;
height: 100%;
min-width: 70px;
min-height: 70px;
max-width: 90px;
max-height: 90px;
object-fit: cover;
border-radius: 0.5rem;
display: block;
}
.image-collection-image-container {
flex: 0 0 auto;
width: 90%;
padding: 2px;
}
.image-collection-image {
border: 1px solid var(--kendo-color-border, rgba(0, 0, 0, 0.08));
}
</style>using Microsoft.AspNetCore.Components;
using Telerik.Blazor.Components;
namespace RazorLibrary.Components.Images;
/// <summary>
/// <para>
/// Displays a collection of selectable image thumbnails provided by <see cref="Images"/> and a main image display area.
/// If <see cref="Images"/> is empty, displays a <see cref="TelerikSkeleton"/> placeholder instead. Supports
/// accessibility, configurable height via <see cref="Height"/>, width via <see cref="Width"/>, aspect ratio control
/// via <see cref="ConstrainImageHeight"/> and <see cref="ConstrainImageWidth"/>, and alt text for images.
/// </para>
/// <para>
/// Usage:
/// <code>
/// @using RazorLibrary.Components.Images
///
///
/// <ImageCollection Images="myImages" Height="300px" Width="100%" />
///
///
/// @code {
/// private List<ImageInfo> myImages = new()
/// {
/// new ImageInfo("img1.jpg", "First image"),
/// new ImageInfo("img2.jpg", "Second image")
/// };
/// }
/// </code>
/// </para>
/// </summary>
public partial class ImageCollection
{
/// <summary>
/// The collection of images to display in the image collection.
/// </summary>
[Parameter] public IEnumerable<ImageInfo> Images { get; set; } = [];
/// <summary>
/// The overall height of the image collection component (e.g., "200px", "100%", or "auto"). Default is "auto".
/// </summary>
[Parameter] public string Height { get; set; } = "auto";
/// <summary>
/// The overall width of the image collection component (e.g., "200px", "100%", or "auto"). Default is "auto".
/// </summary>
[Parameter] public string Width { get; set; } = "auto";
/// <summary>
/// If true, sets the main image display height to 100%, which constrains it to the value specified by <c>Height</c>.
/// If false, height is auto. Maintains aspect ratio unless both <c>FixImageHeight</c> and <c>FixImageWidth</c> are true.
/// Default is false.
/// </summary>
[Parameter] public bool ConstrainImageHeight { get; set; } = false;
/// <summary>
/// If true, sets the main image display width to 100%, which constrains it to the value specified by <c>Width</c>.
/// If false, width is auto. Maintains aspect ratio unless both <c>FixImageHeight</c> and <c>FixImageWidth</c> are true.
/// Default is true.
/// </summary>
[Parameter] public bool ConstrainImageWidth { get; set; } = true;
/// <summary>
/// The magnification scale for the magnifier. Default is 3 (3x magnification).
/// </summary>
[Parameter]
public double MagnifyScale { get; set; } = 3;
/// <summary>
/// Applies additional CSS classes to the ImageCollection's root element for custom styling and visual modifications.
/// </summary>
[Parameter] public string Class { get; set; } = string.Empty;
private ImageInfo? _selectedImage;
/// <inheritdoc />
protected override void OnInitialized()
{
_selectedImage = Images.FirstOrDefault();
}
private void OnImageSelect(ImageInfo imageInfo)
{
_selectedImage = imageInfo;
}
}namespace RazorLibrary.Components.Images;
/// <summary>
/// Information about an image, including the source URL and alt text.
/// </summary>
/// <param name="Src">The image source URL. Required.</param>
/// <param name="Alt">The image alt text. Optional.</param>
public record ImageInfo(string Src, string? Alt = null);The sample code uses the MagnifiableImage component, which is another feature request. The MagnifiableImage component can be replaced with a img element.
Please expose the popup collision settings of the Popup and Popover components to all other applicable components, such as:
I realize we can build Blazor components to associate "label" to controls, but IMHO, this should come standard with any control tool set. Reasons:
1. Coding simplicity
2. Automatic theme inheritance
3. Flexibility in label positioning relative to the control (left, right, top, bottom)
Example:
<TelerikCheckBox Label="Is Chasssis" LabelPosition="left" @bind-Value="equipment.IsChassis"/>
I realize you folks put some effort into a "Floating Label", but my users have rejected this implementation because:
1. Having Text in a label as a means to identify the label makes them think a value is already provided (so they try to remove it or call for support).
2. When typing in the label and the appearance of the label identification appears above adds to their confusion as they are used to seeing validation errors appearing above a label.
Using the TelerikTabSrip, If the first tab is not visible when rendered, the tab content for all tabs doesnt render.
Replicated here https://blazorrepl.telerik.com/cpEWGOPk22VW8be254
If you change the code to make the first tab visible, all is well.
You can make other tabs invisible, and all is well.
Hi,
is it possible to add control of vertical timeline like this (https://antblazor.com/en-US/components/timeline) with extra features like:
I also attached image of what i build based on control from link above, I would like to implement something similar with Telerik.
Maybe you can recommend some other controls to replace it with?
Thank you :)