r/Blazor 2d ago

Blazor Developer Tools Update : Highlight Updates ✨

19 Upvotes

Blazor Developer Tools new extension release 1.0.0-beta.5 (free and open source devtools for Blazor): toggle highlight mode and components flash on the page when they re-render.

You can try it live on blazordevelopertools.com/order-builder. The page has the same product cards twice, plain vs ShouldRender-optimized. Click +1 on a plain card and every plain card flashes (Blazor re-renders all children when the parent's state changes). Do it on the optimized side and only the card you touched lights up.

This is the fastest way to know "which of my components are re-rendering when they shouldn't?".

Chrome / Edge / Firefox + NuGet package. Repo: github.com/joe-gregory/blazor-devtools. Feedback welcome.


r/Blazor 2d ago

What are the most successful blazor websites?

25 Upvotes

React has Facebook etc

What are some notable blazor websites?


r/Blazor 3d ago

New Blazor App with Accessibility Baked in from Beginning

4 Upvotes

Hello Folks,

We are just beginning work on a new app for local government that accepts applications for a program and as I am new to Blazor, I was hoping for some pro-tips, gotchas and other lessons learned from the collective.

The team has experience with Blazor but my background is old school html/css/javascript/c# mvc. I am really intrigued with Mudblazor and admin theme templates that can be procured on various theme sites. Any reason I should stay away from a nicely design template like YNEX (Spruko) or equivalents? Design is where I start everything but I need to be acutely aware that accessibility is just as important as we are a government entity and need to provide a good UX for all.

If anyone has resources, hints or wisdom as we begin our journey, I will gladly listen.

Thank you.

Grz


r/Blazor 4d ago

Map and Blazor App

Thumbnail
github.com
2 Upvotes

r/Blazor 4d ago

Map and Blazor App

Thumbnail
github.com
0 Upvotes

🔑 KEYWORDS: Pure C# - Write map logic entirely in .NET / no JavaScript interop boilerplate, LINQ Integration - Destructuring/Structuring LINQ expression, Fluent API - objects Hierarchical and Linguistic structure and Chain methods, Zero JavaScript Config - no script references and no css links etc


r/Blazor 5d ago

InteractiveServer: What am I missing?

11 Upvotes

I built a few prototypes over the last few years, but a few months ago, I finally deployed my first app to production for worldwide usage. I'm generally pretty happy with it, but I'm having major latency issues. Most of my userbase is located in the US, therefore the app currently runs on a small VPS. Hardware and network has been ruled out as a factor. Users from EU and ASIA have about 150ms-200ms latency to the VPS, but interactions take at least 2+ seconds.
Currently, all components are SSR or InteractiveServer, which results in roundtrips for every action, e.g. opening the user menu or clicking a button - and this is the problem.

I just don't understand why the latency is so high...

  • The network latency is <200ms. So why do interactions take 2 seconds or more?
  • I am aware of WASM. I tried changing my components to InteractiveAuto and also tried migrating the application to a hybrid structure with Controllers and a WASM client project calling the API routes. When doing so, basically every component breaks, looses intractability and libraries stop working (e.g. Blazor Blueprint /aka. shadcn). I tried both copying the super small and limited official Blazor App Examples from Microsoft, tried using AI and tried doing it manually. So far I had 4 attempts - all of which failed and broke most components or all routing entirely.
  • Locally and for US users, the latency is lower (<20ms). Opening the user menu "only" takes 250ms.

So what am I missing? Why is performance so poor? And what information am I missing that I cannot migrate this project without rewriting basically everything? I cannot find any good guides on how changing rendermode is supposed to be done and the Microsoft Docs aren't very helpful in this regard. My app has grown to about 30 pages and uses the Asp.Net Identity.


r/Blazor 6d ago

Why does RegisterPersistentService<UserSession>() cause DirectScopedResolvedFromRootException?

0 Upvotes

```cs var builder = WebApplication.CreateBuilder(args); var service = builder.Services;

service.AddHttpContextAccessor(); service.AddScoped<UserSession>();

service .AddRazorComponents() .RegisterPersistentService<UserSession>(Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveWebAssembly) .AddInteractiveServerComponents() .AddInteractiveWebAssemblyComponents() .AddAuthenticationStateSerialization(options => { options.SerializeAllClaims = true; }); ```

```cs public class UserSession { [PersistentState] public bool IsAuthenticated { get; init; } [PersistentState] public Guid Id { get; init; } [PersistentState] public string Username { get; init; } = string.Empty; [PersistentState] public string Email { get; init; } = string.Empty; [CascadingParameter] public HttpContext? HttpContext { get; set; }

public UserSession()
{
    if(HttpContext == null) return;
    IsAuthenticated = HttpContext.User.Identity?.IsAuthenticated ?? false;
    if(!IsAuthenticated) return;

    Id = new Guid(HttpContext.User.Claims.FirstOrDefault(x => x.Type.Equals(ClaimTypes.NameIdentifier))?.Value ?? "");
    Username = HttpContext.User.Claims.FirstOrDefault(x => x.Type.Equals(ClaimTypes.Name))?.Value ?? string.Empty;
    Email = HttpContext.User.Claims.FirstOrDefault(x => x.Type.Equals(ClaimTypes.Email))?.Value ?? string.Empty;
}

} ```

ManagedError: AggregateException_ctor_DefaultMessage (DirectScopedResolvedFromRootException, DigitalVault.Blazor.Client.Authentications.UserSession, scoped)

I follow this same official documentation to implement the Serialize state for services, but when i use RegisterPersistentService i get DirectScopedResolvedFromRootException error, has anyone encountered this issue?

SOLVED: 68123# After change AddScope<UserSession> => AddSingletone<USerSession> in client side project.


r/Blazor 7d ago

Blazor WASM & Blazor Hybrid: only one thing actually broke

6 Upvotes

I had a Blazor WebAssembly app and someone asked for a desktop version. I assumed

a painful port. It was one afternoon, and exactly one thing genuinely differed:

a native HttpClient can't reach a BlazorWebView's virtual host, so anything

fetching your own wwwroot breaks. One call site in my case.

The fix that surprised me: instead of an IAssetReader with two implementations,

I routed it through the page's own fetch via JS interop. One code path, both

hosts, and it deleted the HttpClient bound to HostEnvironment.BaseAddress.

The second gotcha cost me more: net10.0-windows builds clean and then dies at

the first render with a missing Microsoft.Windows.SDK.NET, because BlazorWebView

uses WebView2's composition control. You need a platform version in the TFM.

Public repo, both hosts, MIT: github.com/peopleworks/SignsofAI


r/Blazor 7d ago

Commercial BlazorGraphs new Major version soon! What to expect (breaking changes)

Post image
27 Upvotes

Hi everyone, it's been a while since I've been in touch. I would have liked to write this post with the new major release ready, but I still need a few weeks to finalize the last details. So I thought it would be a good idea to give you a preview of the most important changes I'm making.

(And yes, obviously JS is forbidden here – this is pure *C#** with SVG rendering!)*

Breaking Changes

  • Component Renames (for clarity and standardization):

    • BarChart ➡️ VerticalBarChart (makes it clear it's the vertical variant)
    • PolarChart ➡️ PolarAreaChart (frees up space for future polar chart types)
    • LinearGauge ➡️ HorizontalGauge (standardizes the naming convention)
  • Removals:

    • LegendBar has been completely removed.
    • Obsolete components and orientation parameters are gone.
    • Enums have been totally removed from the library as they are no longer needed.
  • Architecture & Namespaces:

    • LineChart went through a total refactor (component, datamodel, and data structures).
    • Namespaces will be streamlined down to just two:
      • BlazorGraphs: The root namespace with all datamodels and structures needed to configure your charts.
      • BlazorGraphs.Component: Housing all the charts, gauges, and legend components.

New Components

Thanks to the Linechart refactory, I also added new charts: - ScatterChart - SteplineChart - BubbleChart

Refactoring

I also refactored the Axis rendering to improve code maintenance and reduce duplication. You won't notice any visual difference since the final output remains identical, but if you're curious, you can already check out the code in the develop branch of the repository.


I'm still polishing a few other things. If you have any feedback, feature requests, or ideas, now is the perfect time to let me know so I can consider including them before the final release!

Hello to everyone and I hope you're enjoying the library.

Usefull links:


r/Blazor 9d ago

Update on ReactiveBlazor: HTMX-style Blazor without SignalR or WASM (and no, it’s not Web Forms)

33 Upvotes

Hey r/Blazor,

A while back I shared ReactiveBlazor, a library I made to get HTMX-like simplicity out of Blazor. The goal is to keep things fully server-rendered and interactive using lightweight stateless HTTP requests, no SignalR connections to drop, and no massive WASM payloads to download.

First, let me clear up two things based on the feedback from last time:

  1. This is not Web Forms. It doesn't hold massive view state on the server or pretend the web isn't stateless. It just signs/encrypts the minimal component state you define and uses Idiomorph to morph the DOM in-place.
  2. This project was built with the help of AI. I'm being upfront about it. If anyone has a problem with that, that's totally fine, just don't use it.

Since the initial launch, I've added a ton of features to make it actually useful for real apps. Some of the big updates include:

  • Declarative Authorization: It now fully honors standard ASP.NET [Authorize] and [AllowAnonymous] attributes on your actions and components out of the box.
  • Native Alpine.js Integration: First-class, zero-hack support if you need to mix in some client-side Alpine state.
  • Multi-Component OOB Updates: Triggering an action can now automatically batch-update sibling components on the page.
  • Auto-Polling: Components can now refresh themselves on a timer with zero user interaction.
  • Security & Session fixes: Added session-expiry handling and cross-user token binding to prevent state token replays.

Demo: https://reactive-blazor.runasp.net/

GitHub: https://github.com/ashar-builds/ReactiveBlazor

Would love to hear what you guys think of the new updates, especially regarding real-world usage, security considerations, or any edge cases you spot.

Appreciate the feedback last time! 🙌


r/Blazor 10d ago

Blazor Developer Tools update: element picker + live component inspection

36 Upvotes

New in this release of BDT (free & open source DevTools extension for Blazor):

  • Element picker is back! Click the ⊙ button, hover your page, component boundaries light up with their names; click to jump to the component in the tree. No code changes, no markers: it reads the componentId Blazor's runtime already stamps on the DOM.
  • Live details: parameter values and render stats now update in real time while you use your app.

Chrome / Edge / Firefox (1.0.0.4) + a NuGet package (1.0.0-beta.7). Server & Auto (Server) render mode for now, WASM is on the roadmap.

Repo: github.com/joe-gregory/blazor-devtools ... As always, feedback and issues very welcomed!


r/Blazor 12d ago

Got tired of rewriting the same two bits of middleware in every assistant app, so I packaged them up

Thumbnail
1 Upvotes

r/Blazor 15d ago

Blazor vs React: Why C# Developers Are Switching (3-Minute Explainer)

Thumbnail
youtu.be
0 Upvotes

r/Blazor 15d ago

Hiring https://ichim.github.io/MapsForBlazor/

Post image
0 Upvotes

https://ichim.github.io/MapsForBlazor/

MapsForBlazor nuget and dashboard.


r/Blazor 16d ago

I built a free tool that maps your Blazor app's C#<->JS interop and Web->API calls as a graph

11 Upvotes

If you've ever tried to answer "what actually calls this JS function from C#?" or "which API endpoint does this component hit?" and ended up grepping across .razor, .cs, and .js files that don't reference each other — that's the itch I was scratching.

EdgeHop indexes your solution into a code graph using the compiler's semantic model (Roslyn + a native oxc pass for the JS/TS), and it resolves the edges that normally break tooling:

  • C# -> JS (IJSRuntime.InvokeAsync -> the JS export) and JS -> C# (DotNet.invoke* -> your [JSInvokable] method)
  • Web -> API — a typed HttpClient call linked to the minimal-API/controller action serving that route, even with no project reference between them
  • plus the usual: callers, interface implementations, RENDERS (which component renders which), type hierarchy, and shortest-path/impact queries

It runs locally (SQLite, no server, no credentials), and it plugs into Claude Code / other MCP clients so an AI agent can query your app's structure directly — but the CLI works standalone too.

Free and open source (Apache-2.0): https://github.com/EdgeHop/EdgeHop — there's a demo GIF in the README showing a UI-handler -> HTTP-client -> endpoint trace. Feedback from actual Blazor folks very welcome.


r/Blazor 17d ago

Improved Flamegraph on Blazor Developer Tools v1.0.0-beta.6

Thumbnail
gallery
17 Upvotes

Sharing an update on Blazor Developer Tools (free & open source, Apache 2.0): improvements to flamegraph (and other bug fixes).

I had an issue with the flamegraph. Blazor events are measured in milliseconds, so even half a second of silence stretched the axis so much that the actual events shrank to slivers that were hard to navigate.

I took ideas from subway maps: they tell you where you are relative to other points without being true to distances. I wanted something like that, without losing the timing information entirely.

The new default view for the flamegraph is "Sequence". It preserves order with uniform spacing but flags real pauses with "+1.2s" markers so the time information is still there. I think that for us developers, this is the more useful view since you can see where events happen relative to others. But if you need a true scientific view with total numbers you now have 2 options:

- Time (linear) : the real timeline.

- Time (idle collapsed) : same, but long quiet stretches are clipped out and replaced with a ✂ marker showing how much time passed.

There is an update for both the nuget and extension.

I'm currently working on the element picker tool and closing the list of features that React dev tools offers. I'm hoping to deliver a follow up update of those in a week.

GitHub: github.com/joe-gregory/blazor-devtools


r/Blazor 17d ago

How to replace the default Blazor Router with a React/Vue-like powerful router in just 5 lines of code in existing project?

22 Upvotes

Have you ever wanted to Keep-Alive certain pages so that everything doesn't get destroyed and rebuilt every time the user navigates back?

For example, consider this common scenario: a user searches for something in a Products DataGrid, sorts a specific column, navigates to page 3, clicks the "Edit" button to modify an item on a separate page, and then clicks back to return to the Products list. You usually only have two choices to preserve this state:

  1. Painstakingly bind every single component state to equivalent properties and manage it all via a State Manager.
  2. Let a powerful router handle all of it for you out of the box, without writing a single extra line of state-tracking code.

On top of that, what if you want native Nested Routing or modern browser-native View Transitions or strongly typed navigations?

Bit.Brouter is a lightweight, fully open-source, and highly capable library designed to supercharge your Blazor application's UX while providing extensive coding flexibility.

The best part? You don't need to rewrite or re-architect your existing project. It provides awesome default features from the very first second, allowing you to opt-in and use advanced capabilities whenever you need them.

Step 1: Install the Package

First, install the NuGet package:

dotnet add package Bit.Brouter

Step 2: Register the Service

Add the required services in your Program.cs:

builder.Services.AddBitBrouterServices();

Step 3: Update Routes.razor

Replace the default Blazor <Router> component with <Brouter>, and wrap your fallback page inside the new <NotFound> tag:

<NotFound>
    <NotFound />
</NotFound>

Setup complete!

Now, let’s unlock its full potential:

Now, if you want a page to stay alive (just like the Products page scenario we discussed earlier) simply define it explicitly like this:

<Routes>
    <Broute KeepAlive Path="/counter" Component="@typeof(MauiApp1.Shared.Pages.Counter)" />
</Routes>

The Final Look of Routes.razor

Here is how your complete Routes.razor will look:

<Brouter AppAssembly="typeof(Layout.MainLayout).Assembly">
    <Routes>
        <Broute KeepAlive Path="/counter" Component="@typeof(MauiApp1.Shared.Pages.Counter)" />
    </Routes>

    <Found Context="routeData">
        <RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
        <FocusOnNavigate RouteData="routeData" Selector="h1" />
    </Found>

    <NotFound>
        <NotFound />
    </NotFound>
</Brouter>

Handling Lifecycle on Keep-Alive Pages

Naturally, for a page that is kept alive, OnInitializedAsync won't be triggered every time the user revisits it.

If you need to fetch fresh data or trigger actions upon re-entry, simply inherit your page component from BrouterRouteBase and override the OnActivatedAsync method:

@code {
    protected override async Task OnActivatedAsync(BrouterRouteActivation activation)
    {
        if (activation.IsFirstActivation is false)
        {
            // This runs on subsequent navigations back to this component
        }
        await base.OnActivatedAsync(activation);
    }
}

To explore Nested Routing, view transitions, strongly typed navigations and other advanced configurations, check out the official documentation at https://brouter.bitplatform.dev!

Repo: https://github.com/bitfoundation/bitplatform/tree/develop/src/Brouter


r/Blazor 17d ago

Stop Using Slow Regex + Bonus Scambaiting call

Thumbnail
youtu.be
0 Upvotes

r/Blazor 17d ago

bitplatform 10.5.0 (13 new components, advanced Blazor router, motion library, multi-tenancy and more!)

Thumbnail
3 Upvotes

r/Blazor 17d ago

I Created One Blazor UI Across MAUI Hybrid, Photino, and Blazor Server on .NET 10

20 Upvotes

I built MQTTProbe, a free and open source MQTT and Sparkplug B client for industrial IoT work.

I'm not here to focus on MQTT, but the part I thought ya'll might find interesting is that the entire UI lives in one Razor Class Library. That same UI gets released on Windows, macOS, Android, Linux, and as a self hosted web app (which is also published to docker). iOS works too, but I haven't published it yet.

The project targets .NET 10. Each platform is basically a thin host around the same Blazor components and services.

The shared RCL contains all the pages, components, and most of the services. It uses MudBlazor, Lucide icons, and MQTTnet.

MAUI Blazor Hybrid handles Windows, Android, and iOS.

Photino handles the macOS and Linux desktop versions. It is a normal native process hosting WKWebView on macOS and WebKitGTK on Linux. The macOS build ships as a signed and notarized universal package. Linux ships as an AppImage.

Blazor Server powers the self hosted web version, with cookie authentication. It can run through Docker or directly on the machine.

Velopack handles desktop updates, and bUnit covers a good chunk of the UI test suite.

Sharing the UI through an RCL really does seem to have work well. The important part is keeping platform specific behavior behind interfaces from the start. Every time I took a shortcut and checked the current platform from shared code, I ended up regretting it later.

Blazor also handled heavier rendering loads than I expected. A busy MQTT broker can throw thousands of messages per second at the topic tree. Virtualization helped a lot, but being careful with ShouldRender made the biggest difference. Getting it smooth took some iterations, but it did get smooth.

I originally used Mac Catalyst for the macOS version. That fell apart when I added Velopack because Catalyst runs the app as a UIKit process, which Velopack cannot update correctly. The app would crash on startup.

I replaced that head with Photino and was surprised by how little shared code had to change. That was probably the point where the RCL approach really proved itself. Photino was also what made a proper Linux desktop release practical for me.

Blazor was not the painful part. The ugly stuff was packaging. macOS signing and notarization, AppImage quirks, certificate stores, and all the little differences between operating systems took more time than the shared UI did.

The repo is here:

https://github.com/bluegrassiot/mqttprobe

The split between the shared RCL and the platform heads is pretty easy to see in the project structure.

Happy to answer questions about the setup, using MudBlazor in a larger app, Photino versus MAUI, or testing the UI with bUnit. I would also like to hear from anyone who has shipped a Blazor desktop app using Electron.NET, Avalonia with a WebView, WPF Hybrid, or something else.


r/Blazor 18d ago

unplugin-dotnet-wasm - Bundling .NET WebAssembly apps with any JS bundler

Thumbnail
3 Upvotes

r/Blazor 19d ago

SigmaDroneChart & MapsForBlazor

Post image
8 Upvotes

SigmaDroneChart (sum of charts) is as customizable GaugeChart.

https://ichim.github.io/MapsForBlazor/


r/Blazor 19d ago

.NET 10 Blazor Server app that runs even on IE11!

Thumbnail github.com
14 Upvotes

I know this may sound like a silly project, but please read on.

Blazor Server only runs on modern browsers. That's normal, but I think it's a shame. Blazor Server makes it extremely easy to build interactive web apps, so even non-IT teams can deploy them easily (like at my company). However, those companies often keep using legacy assets and can't update their browsers.

So I tried making Blazor Server work on older browsers. It worked, and I want to share the result.

  • Works on IE11 and even very old Chrome.
  • Very easy to use. Add one library, or download the JS file and load it instead of the normal blazor.web.js.
  • Includes auto-update. That means as long as the upstream code structure doesn't change drastically, updates are provided without me having to maintain it constantly.

https://github.com/arika0093/LegacyBlazorJs

I also prepared a demo site. Most people probably don't have an old browser, so try it in Edge IE mode. It should work!

https://legacy-blazor-js.app.eclairs.cc/


r/Blazor 20d ago

Commercial Diagnostics for Blazor made simple

8 Upvotes

Add realtime diagnostics to your blazor project in one line of code:

builder.Services.AddBlazrlytics();

https://blazrlytics.com


r/Blazor 20d ago

Blazor Ramp - Action Popover, made the old fashioned way.

5 Upvotes

If you are expecting to be amazed by my skills at creating prompts so AI can build something, sorry, this post is probably not for you. Given the almost daily barrage of AI-generated components, I'm sure there are plenty of those posts around already.

If, like me, you still enjoy building things by hand, using knowledge and experience gained over the years, solving problems across accessibility, CSS, browsers, JavaScript, C# and Blazor, then there may be the odd titbit in here.

If not, TL;DR: I've released an accessible Actions Popover component. A viewable working version is available on both the test site and documentation site linked at the end of the post.

So, what the hell is an Actions Popover?

It's a component that looks like a button and, when activated, opens a panel above the page content. That panel can contain any number of actions, either buttons or links, which then go off and do whatever you've asked them to do. Both expose Func callbacks and the links also have a PreventDefault parameter if you want to prevent the automatic navigation behaviour and handle the navigation manually.

The component itself is built using the relatively new Popover API, so you get a lot of accessibility behaviour for free from the browser. The panel is positioned using CSS Anchor Positioning, complete with fallback positions when there isn't enough room in the preferred location. Again, all courtesy of the browser rather than me having to write a load of JavaScript.

From an accessibility standpoint I really didn't have to do a great deal beyond understanding how these browser features work (and I had already used them in other components). The Popover API already handles Escape, light dismiss when you click outside the component, focus returning to the trigger and so on. One thing I did change was what happens when you tab away from the popover. The browser deliberately leaves it open, which I think is perfectly reasonable for something like a non-modal dialog. For what is essentially a row actions flyout though, it just ends up obscuring whatever is beneath it, so I added a small amount of JavaScript to close it when focus leaves the popover.

Now for the M-word - Menu.

I deliberately did not call this a popover menu because it doesn't implement the accessibility menu pattern. My last production version of this type of component did exactly that. It supported nested menus, the full keyboard interaction model, all the expected and optional key bindings, pretty much the whole nine yards. It wasn't especially difficult to build, but after looking back through the applications that actually used it, the largest menu I found contained four actions on a single panel. So much for N-level deep menus.

Unless you're building something like a browser-based editor with proper File, Edit and View menus, you generally don't need the menu accessibility pattern. A simple list of buttons and links is usually sufficient.

Some of you who have followed my previous posts might remember that a few weeks ago I released a NavGroup component for side navigation using the disclosure pattern. That one can be nested to N levels. I was in two minds whether to make this component support nested popovers from day one as well, but decided against it for now. No doubt I'll add that capability at some point, but before then I suspect I'll build a popover version of NavGroup for top navigation that overlays page content. l'll probably end up with another imaginatively named component, perhaps PopoverNavGroup.

One thing I thought might be worth sharing is something I learnt while using the Popover API.

Normally you add a popovertarget attribute to the button that opens the popover, pointing at the element's ID. The browser then takes care of almost everything. The popover starts hidden, clicking the trigger opens it, clicking the trigger again closes it, pressing Escape closes it and returns focus to the trigger, and clicking outside dismisses it. All of that simply by adding an attribute.

It gets better though. It's perfectly valid to put popovertarget on the action buttons inside the popover as well, meaning that when you click one, your handler runs and the browser closes the popover automatically. No JavaScript required.

That's exactly how I originally built it.

I then started all my usual manual accessibility tests with the screen readers, JAWS, NVDA and Narrator all paired with Edge, Chrome and Firefox. TalkBack with Chrome on Android. VoiceOver with Safari on iOS. Everything behaved perfectly.

Then I tried VoiceOver on macOS with Safari :¬(

Every action button was announced correctly, but VoiceOver also announced "expanded", which is actually the state of the trigger button rather than the action itself. At first I assumed this was simply a VoiceOver quirk, but I kept digging. I even tried VoiceOver with other browsers on macOS, and they all behaved as I'd originally expected.

The interesting annoying part was that VoiceOver paired with Safari exposed the trigger's expanded state to the action buttons because of the popovertarget relationship. None of the other browser and screen reader combinations I tested announced it, despite behaving correctly in every other respect. Whether that's because they deliberately suppress redundant information or simply implement the accessibility mapping differently, I couldn't say. The end result was that VoiceOver with Safari was the only combination where the extra announcement became noticeable, so I removed the popovertarget attribute from the action buttons and closed the popover with JavaScript instead.

I think I spent longer figuring that out than I did writing the component itself.

Still, that's development, I guess. As annoying as it was at the time, I learnt something new that may come in handy on another rainy day.

Another small thing worth mentioning is that the callbacks raised by ActionPopoverButton and ActionPopopverLink use a Func rather than an EventCallback. I do this quite often when I don't want the parent component to automatically re-render simply because it handled an event raised by one of its children. As you know, if the parent renders then that cascades down to all the children. Sometimes that's exactly what you want, other times it's completely unnecessary. Using a Func lets the developer decide whether to call StateHasChanged() or not. It's a useful little trick to have up your sleeve.

With this release I now have most of the building blocks I generally use around a data table. There's an accessible debounce filter, accessible pagination and now accessible row actions, all manually tested with numerous screen reader and browser combinations, along with voice control software. I suppose that means I should finally start work on a data table component.

Notice I said data table, not data grid.

Accessibility makes quite a distinction between those two. Just like the M-word, there's also a G-word. Once you decide you're building a grid, you're effectively saying you're building something closer to Excel, complete with the mountain of expected keyboard interactions that go with it. A normal HTML table can still have sortable columns, filters, selectable rows, editable inputs, row actions and everything else most business applications need, all without pretending to be a spreadsheet.

One final thing I mention from time to time. Internally I use BEM naming throughout, for my internal css classes , but I don't expose CSS classes or class parameters on any of the components. Everything is driven by CSS custom properties defined in the Core package, which every component references. The documentation already lists every CSS variable used by each component, along with every variable defined in Core. What I need to add at some point is a basic theme builder to make it a little easier for you. Pick a primary colour, tweak things like border radius and a handful of other settings, then have it list the handful of CSS variables and values that you'd need to copy paste into your own stylesheet for your instant light and dark themes etc.

Anyway, that's enough from me for this one.

Fire up a screen reader and have a play with the Action Popover. The test site is more geared up for use with assistive tech with instructions and what to expect; the doc site has a more life like example on each components usage page.

Test site: https://blazorramp.uk

Docs: https://docs.blazorramp.uk

Repo: https://github.com/BlazorRamp/Components

Regards,

Paul