I needed a few basic controls for a side project and I refuse to pull in a massive JS bundle or write interop code if I don't have to. So I spent way too much weekend time building four common components using strictly C# and CSS. No IJSRuntime, no hidden script tags in wwwroot, and no node_modules folder blowing up my disk space.

Here is what I ended up with:

  • A carousel. Full of dogs because obviously.
  • A credit card form. Complete with a live 3D card flip when you hit the CVC field.
  • A hover card. Preview links on hover without needing an external library.
  • A questionnaire. Built as a Batman quiz because testing things with boring lorem ipsum forms is miserable.
Visit the demo site!

Why avoid JS here?

Honestly, every time you reach for JS interop in Blazor, you're introducing a second language to debug when something blows up at 11 PM. If you keep the state logic in C# and let modern CSS handle the visual presentation (like animations and 3D transforms), life is just easier. Breakpoints work, refactoring is painless, and you can literally copy-paste a .razor file into another project without worrying about missing script imports.

1. The Carousel (The Radio Button Trick)



This one is my favorite because it uses zero C# state and zero JavaScript. The browser does all the heavy lifting using hidden radio buttons.

Every slide has an associated radio input right before it. Since HTML radio groups inherently know how to handle "only one option selected at a time," they act as the state engine. The next and prev buttons are just <label> tags pointing to those radios.

<input type="radio" class="carousel-control" name="@groupName" id="@SlideId(index)" checked="@(index == 0)" />
<figure class="carousel-slide">
    <img src="@slide.ImageUrl" alt="@slide.AltText" />
    <label class="carousel-prev" for="@SlideId(Wrap(index - 1))">‹</label>
    <label class="carousel-next" for="@SlideId(Wrap(index + 1))">›</label>
</figure>

A tiny bit of CSS toggles the visibility based on what's checked:

.carousel-slide { opacity: 0; visibility: hidden; transition: opacity .6s; }
.carousel-control:checked + .carousel-slide { opacity: 1; visibility: visible; }

Bonus: radio groups give you keyboard arrow navigation out of the box, and using a unique Guid for each carousel instance keeps multiple galleries on the same page from breaking each other.

2. The Credit Card Form



This mimics those popular React credit card components, but written natively for Blazor. As you type, it detects the card brand (Visa, Amex, Mastercard, etc.), formats the spacing correctly, and runs a Luhn check.

When you tab over to the CVC field, the card flips over. The 3D rotation is purely CSS:

.cc-card-inner { transform-style: preserve-3d; transition: transform .7s; }
.is-flipped .cc-card-inner { transform: rotateY(180deg); }
.cc-front, .cc-back { backface-visibility: hidden; }
.cc-back { transform: rotateY(180deg); }

C# just handles the logic for when it should flip:

private bool IsFlipped => Focused == CardField.Cvc && Brand.Id != "amex";

(And obviously, don't use a homemade form for real production payment processing unless you want compliance nightmares—use your payment provider's hosted fields for actual apps).

3. The Hover Card



Modeled after shadcn/ui. Hover a link, wait a beat, and a preview pops up. Move your mouse to the card itself, and it stays open.

Instead of messing around with setTimeout and clearTimeout like you would in JS, you just lean on Task.Delay and a CancellationTokenSource:

private async Task RequestAsync(bool open, int delay)
{
    pending?.Cancel();          
    pending?.Dispose();
    pending = null;

    if (delay > 0)
    {
        pending = new CancellationTokenSource();
        try { await Task.Delay(delay, pending.Token); }
        catch (TaskCanceledException) { return; }
    }

    isOpen = open;
    await OpenChanged.InvokeAsync(open);
}

Every new mouse movement cancels the previous pending delay. Simple, clean, and entirely contained in C#.

4. The Questionnaire



I built a questionnaire component and used a Batman trivia quiz as the home page demo. It handles single-choice radios, multi-select checkboxes, custom freeform text inputs, keyboard shortcuts (press A, B, C to answer, Enter to advance), and focus management via ElementReference.FocusAsync().

One quick gotcha I ran into: don't mix native form submission handlers with Blazor's @onkeydown event. My initial setup let the browser's default "press Enter to submit" logic fire at the same time as my custom Enter key listener, which caused a single keystroke to skip two questions at once.

The fix is to explicitly intercept the form submission so Blazor takes full control:

<form @onsubmit="() => { }" @onsubmit:preventDefault @onkeydown="HandleKeyAsync">

Wrapping up

You really don't need an npm dependency for everything. Most UI interaction patterns can be solved cleanly with a bit of modern CSS and standard C#.

Anyway, I'm going back to fix my score on the Batman quiz because getting "Gotham Rookie" was deeply humbling.


[source code]

Comments

Popular posts from this blog

Customizing PWA Manifest and Icons for a Polished User Experience 🚀

Offline-First Strategy with Blazor PWAs: A Complete Guide 🚀

Yes, Blazor Server can scale!