Most frontend developers eventually hit the same wall: HTML and CSS are built around boxes. You can round the corners, mask them, transform them, but underneath it all, they're still rigid, block-level rectangles pretending to be something else.
ZikFash — the offline-first tailoring PWA I've been writing about for a few posts now — ran straight into that wall. Tailors needed a digital workspace where they could configure garment cuts, patterns, and overlapping layout layers, and see the result instantly. None of that is a rectangle, and all of it needed to redraw the moment a C# variable changed.
If you were tasked with building this today, your mind would probably jump to one of two legacy solutions:
- The Brute-Force Image Stack: Generate thousands of transparent PNG variations and absolutely destroy the user's bandwidth.
- The Heavy Canvas Library: Pull in a massive JavaScript library like Fabric.js, drop an imperative
<canvas>element into your DOM, and write thousands of lines of state-syncing JS code across the WebAssembly boundary.
Neither of these solutions work when you are building for unpredictable network connectivity. You need something lightweight, infinitely scalable, and declarative.
The solution? Pure, raw SVG manipulated entirely by C# state variables in Blazor WebAssembly.
The Architecture: Why Blazor and SVG Fit So Well Together
Here's the part most developers forget: SVG is just XML text markup. Because Blazor treats SVG tags (<svg>, <path>, <g>) exactly the same as HTML tags, you can bind your C# variables directly to the mathematical attributes of vector graphics, the same way you'd bind a string to a <div>.
Instead of calling canvasContext.lineTo(x, y) imperatively in JavaScript, we can declaratively state:
“Hey Blazor, the
<path>attributedshould be this exact string generated by my C# function, and if the user changes their layout preference, update the string and let the native browser re-render it.”
This gives us the buttery-smooth performance of raw vector graphics, combined with the safety, strong typing, and diffing engine of C#.
The Core Component Implementation
Here is the exact production-grade component implementation. It utilizes a custom virtual coordinate system, handles dynamic path generation using C# mathematical layout calculations, and utilizes SVG <clipPath> configurations to cleanly isolate overlapping elements without cross-boundary bleeding.
@* Optimized Solution: Zero-allocation, declarative DOM binding where Blazor's
diffing tree updates only string attributes, letting the native browser render vectors.
*@
<div class="style-canvas-container" style="width: 100%; max-width: 400px; margin: 0 auto;">
<svg viewBox="0 0 200 400" preserveAspectRatio="xMidYMin meet" xmlns="http://www.w3.org/2000/svg">
<defs>
@if (IsLayerClipped)
{
<clipPath id="outer-layer-clip">
<path d="@GetOuterShellPath()" />
</clipPath>
}
</defs>
<path d="M 100,12 C 112,12 116,25 120,40 L 120,380 L 80,380 Z" fill="#E2E8F0" />
<g clip-path="@(IsLayerClipped ? "url(#outer-layer-clip)" : null)">
<path d="@GetGarmentGeometry(Config.StyleCut)" fill="@Config.PrimaryColor" />
@if (Config.PatternName != "plain")
{
<path d="@GetGarmentGeometry(Config.StyleCut)" fill="url(#pattern-@Config.PatternName)" opacity="0.65" />
}
</g>
</svg>
</div>
@code {
[Parameter] public CanvasConfig Config { get; set; } = new();
private bool IsLayerClipped => Config.CanvasMode == "bounded";
private string GetOuterShellPath() => "M 20,60 L 180,60 L 160,390 L 40,390 Z";
private string GetGarmentGeometry(string cutStyle)
{
// C# "Digital Scissors" - Transforming layout logic into pure vector strings
string edgeCurve = cutStyle.ToLowerInvariant() switch
{
"angled" => "L 100,85 L 82,52",
"square" => "L 108,65 L 92,65 L 82,52",
"curved" => "C 115,70 105,85 100,75 C 95,85 85,70 82,52",
_ => "C 108,58 92,58 82,52" // Default Baseline Cut
};
return $"M 44.5,65.5 L 7.5,115.5 L 39.5,159.5 L 58,112 L 142,112 L 195,115 L 159.5,65.5 {edgeCurve} Z";
}
public class CanvasConfig
{
public string StyleCut { get; set; } = "default";
public string PrimaryColor { get; set; } = "#B8955A";
public string PatternName { get; set; } = "plain";
public string CanvasMode { get; set; } = "standard";
}
}
What This Actually Buys You
Three things fall out of this approach for free, and none of them needed a single line of JavaScript.
Zero JS interop overhead. There's no heavy data string crossing the WebAssembly boundary, because the SVG is manipulated directly as a native DOM node inside Blazor's own diffing tree. The serialization tax that made our sync engine freeze for a second in an earlier post simply doesn't exist here — there's no boundary left to cross.
Hardware-accelerated rendering, for free. Because every layout calculation resolves down to a plain SVG tag, the browser engine hands the actual rasterization to the GPU. That keeps a locked 60 FPS update cycle even on the lower-end Android phones a lot of tailors are actually using in the field.
A featherweight payload. No high-resolution image assets, no multi-hundred-KB rendering library. The entire interactive canvas runs on less than 100KB of text-based vector coordinates — small enough to sit comfortably inside an app that has to work when the network doesn't.
That last point is really the whole thesis of building ZikFash offline-first: every architectural decision, from the sync engine to this canvas, gets judged by the same question — does this still work with zero bars? SVG-as-C#-state passes that test better than anything else I tried. Sometimes the most "advanced" solution is just remembering what the platform already gives you for free.