<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: BlazOrbit</title>
    <description>The latest articles on DEV Community by BlazOrbit (@blazorbit).</description>
    <link>https://dev.to/blazorbit</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3908471%2Ffa3f179a-bf9a-4768-97bd-0237256cd6f9.png</url>
      <title>DEV Community: BlazOrbit</title>
      <link>https://dev.to/blazorbit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/blazorbit"/>
    <language>en</language>
    <item>
      <title>[Boost]</title>
      <dc:creator>BlazOrbit</dc:creator>
      <pubDate>Sun, 03 May 2026 08:45:47 +0000</pubDate>
      <link>https://dev.to/blazorbit/-561p</link>
      <guid>https://dev.to/blazorbit/-561p</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/blazorbit/lessons-learned-building-a-blazor-component-library-eho" class="crayons-story__hidden-navigation-link"&gt;Lessons learned building a Blazor component library&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/blazorbit" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3908471%2Ffa3f179a-bf9a-4768-97bd-0237256cd6f9.png" alt="blazorbit profile" class="crayons-avatar__image" width="393" height="504"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/blazorbit" class="crayons-story__secondary fw-medium m:hidden"&gt;
              BlazOrbit
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                BlazOrbit
                
              
              &lt;div id="story-author-preview-content-3602787" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/blazorbit" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3908471%2Ffa3f179a-bf9a-4768-97bd-0237256cd6f9.png" class="crayons-avatar__image" alt="" width="393" height="504"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;BlazOrbit&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/blazorbit/lessons-learned-building-a-blazor-component-library-eho" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;May 3&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/blazorbit/lessons-learned-building-a-blazor-component-library-eho" id="article-link-3602787"&gt;
          Lessons learned building a Blazor component library
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/architecture"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;architecture&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/dotnet"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;dotnet&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/blazor"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;blazor&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/blazorbit/lessons-learned-building-a-blazor-component-library-eho#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Lessons learned building a Blazor component library</title>
      <dc:creator>BlazOrbit</dc:creator>
      <pubDate>Sun, 03 May 2026 06:40:38 +0000</pubDate>
      <link>https://dev.to/blazorbit/lessons-learned-building-a-blazor-component-library-eho</link>
      <guid>https://dev.to/blazorbit/lessons-learned-building-a-blazor-component-library-eho</guid>
      <description>&lt;p&gt;I spent the last few months building &lt;strong&gt;BlazOrbit&lt;/strong&gt;, a component library for Blazor. It's not the first of its kind —MudBlazor, Radzen and Blazorise already exist— so I had to answer a hard question from the start: &lt;strong&gt;why does this need to exist?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The answer turned out to be a set of architectural decisions I want to share, because each one taught me something about building UI frameworks that I didn't know before I started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 1: CSS classes don't scale in libraries
&lt;/h2&gt;

&lt;p&gt;The standard pattern in component libraries is class toggling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-primary btn-lg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Click me&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I don't think this is developer-friendly, and there are several points worth making:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Name collisions&lt;/strong&gt;: Your &lt;code&gt;.btn-primary&lt;/code&gt; will clash with Bootstrap, Tailwind, or the consumer's own CSS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combinatorial explosion&lt;/strong&gt;: A button can be small, large, disabled, loading, outlined, filled, errored, full-width, with ripple, without ripple... The number of class combinations grows exponentially.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No override surface&lt;/strong&gt;: If the consumer wants all buttons to be purple, they have to override every modifier combination or use &lt;code&gt;!important&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I experimented with BEM, then utility-first CSS, then CSS-in-JS. None of them solved the problem — or maybe none of them just clicked for me.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I chose
&lt;/h3&gt;

&lt;p&gt;Using a custom &lt;code&gt;&amp;lt;bob-component&amp;gt;&lt;/code&gt; tag and communicating state through &lt;strong&gt;data attributes&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;bob-component&lt;/span&gt;
  &lt;span class="na"&gt;data-bob-component=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
  &lt;span class="na"&gt;data-bob-size=&lt;/span&gt;&lt;span class="s"&gt;"large"&lt;/span&gt;
  &lt;span class="na"&gt;data-bob-disabled&lt;/span&gt;
  &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"--bob-inline-background: #6200ee;"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;Click me&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/bob-component&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Being aware that engines are more optimized for class-based selection, I analyzed the potential cost [1]. It came out to roughly ~0.1ms for a standard DOM. In exchange:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Readability: It's easier to read &lt;code&gt;[data-bob-size="small"]&lt;/code&gt; in CSS than a tangle of dynamic classes.&lt;/li&gt;
&lt;li&gt;Consistency: Component state (active, disabled, etc.) makes more sense as a data attribute.&lt;/li&gt;
&lt;li&gt;Decoupling: Logic from styles. Which might sound contradictory if you think about it, since data attributes do affect styles. The point is that the attribute marks a dynamic state, while the class identifies/defines the design group.&lt;/li&gt;
&lt;li&gt;Testing: Easier automated testing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Along the same chain of decisions, there's the question of how to standardize CSS files.&lt;br&gt;
In the end, the most practical approach was having the global CSS bundle select on &lt;code&gt;[data-bob-component="button"]&lt;/code&gt; and &lt;code&gt;[data-bob-size="large"]&lt;/code&gt;. And the scoped component CSS declares private variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-bob-component&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"button"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--_button-background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--bob-inline-background&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--palette-primary&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-bob-component&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"button"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_button-background&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives consumers three override levels that compose cleanly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Instance&lt;/strong&gt;: pass the &lt;code&gt;BackgroundColor&lt;/code&gt; parameter&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Theme&lt;/strong&gt;: redefine &lt;code&gt;--palette-primary&lt;/code&gt; in their CSS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Global CSS&lt;/strong&gt;: target &lt;code&gt;[data-bob-component="button"]&lt;/code&gt; directly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No &lt;code&gt;!important&lt;/code&gt;. No specificity wars. No class leakage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 2: Reflection is fast if you only do it once
&lt;/h2&gt;

&lt;p&gt;After deciding to group components based on &lt;code&gt;IHas*&lt;/code&gt; interfaces (IHasSize, IHasBorder, IHasPrefix...), which let me unify component rendering and standardize their implementation, I got worried about performance. Each component render needs to know which &lt;code&gt;IHas*&lt;/code&gt; interfaces it implements in order to emit the right attributes. Doing reflection per render would be catastrophic.&lt;/p&gt;

&lt;p&gt;The solution was a &lt;code&gt;[Flags]&lt;/code&gt; enum cache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Flags&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;ComponentFeatures&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;uint&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Variant&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1u&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1u&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Density&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1u&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Loading&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1u&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// ... 22 flags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the first encounter with a component type, we inspect its interfaces and store the bitmask in a &lt;code&gt;ConcurrentDictionary&amp;lt;Type, TypeInfo&amp;gt;&lt;/code&gt;. Subsequent renders of the same component type read the cached flags. The expensive path runs &lt;strong&gt;once per type, per process&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And I went a bit further. Not all attributes change at the same frequency. &lt;code&gt;Size&lt;/code&gt; and &lt;code&gt;Density&lt;/code&gt; change when the parent reconfigures the component. &lt;code&gt;Loading&lt;/code&gt;, &lt;code&gt;Disabled&lt;/code&gt;, and &lt;code&gt;Error&lt;/code&gt; toggle on every user interaction. Rebuilding the full attribute set on every &lt;code&gt;StateHasChanged&lt;/code&gt; would be wasteful.&lt;/p&gt;

&lt;p&gt;So I split the pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BuildStyles&lt;/strong&gt; (runs on &lt;code&gt;OnParametersSet&lt;/code&gt;): full rebuild, reflection cache hit, all attributes computed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PatchVolatileAttributes&lt;/strong&gt; (runs on &lt;code&gt;BuildRenderTree&lt;/code&gt;): only rewrites the 7 high-frequency state attributes. No dictionary allocation. No reflection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In DEBUG builds we instrument the pipeline with &lt;code&gt;IBOBPerformanceService&lt;/code&gt;. On a regular PC, &lt;code&gt;PatchVolatileAttributes&lt;/code&gt; costs ~0.02ms per component. Fast enough not to worry about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 3: A lot of customization is not enough customization
&lt;/h2&gt;

&lt;p&gt;I obviously put a lot of effort into making things customizable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Variable exposure&lt;/li&gt;
&lt;li&gt;Theme creation. Dark and light mode...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But even so, I decided (again) to go to war and design an extensible variant system.&lt;/p&gt;

&lt;p&gt;In most libraries, a variant is an &lt;code&gt;if&lt;/code&gt; branch inside the &lt;code&gt;.razor&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@if (Variant == "primary")
{
    &amp;lt;button class="btn-primary"&amp;gt;...&amp;lt;/button&amp;gt;
}
else if (Variant == "secondary")
{
    &amp;lt;button class="btn-secondary"&amp;gt;...&amp;lt;/button&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or it just modifies a CSS class.&lt;/p&gt;

&lt;p&gt;This is simple and obvious. It's also fundamentally closed. If the consumer wants a &lt;code&gt;Gradient&lt;/code&gt; variant, they have to fork the library or wrap the component.&lt;/p&gt;

&lt;p&gt;I tried several approaches to open it up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IIncrementalGenerator&lt;/strong&gt;: Which generated variants based on an enum. This implementation was actually in the first component library I built, which was really more of a prototype for the current BlazOrbit. It worked well, but it was a nightmare to extend due to the sheer amount of generated code. Plus it hid how everything worked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inheritance&lt;/strong&gt;: Doesn't fit with defining Variant as a component attribute.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I came up with something I'm still not entirely sure is brilliant, a fever dream, or a monumental blunder — but the truth is that in BlazOrbit you can use a &lt;strong&gt;registry pattern&lt;/strong&gt; to define component variants. Just like registering a service.&lt;br&gt;
&lt;code&gt;Func&amp;lt;TComponent, RenderFragment&amp;gt;&lt;/code&gt; delegates are stored in a singleton dictionary, keyed by &lt;code&gt;(ComponentType, VariantType, Name)&lt;/code&gt;. The component core then retrieves them.&lt;/p&gt;

&lt;p&gt;It looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddBlazOrbitVariants&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForComponent&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;UIButton&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
     &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddVariant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MyVariants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Gradient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;component&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;__builder&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
     &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="err"&gt;="&lt;/span&gt;&lt;span class="nc"&gt;gradient&lt;/span&gt;&lt;span class="s"&gt;" @attributes="&lt;/span&gt;&lt;span class="n"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ComputedAttributes&lt;/span&gt;&lt;span class="s"&gt;"&amp;gt;
&lt;/span&gt;             &lt;span class="n"&gt;@component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChildContent&lt;/span&gt;
         &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
     &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The variant receives the live component instance. It can access &lt;code&gt;ComputedAttributes&lt;/code&gt;, parameters and state. It can restructure the DOM entirely. And it lives in &lt;strong&gt;the consumer's&lt;/strong&gt; code, not the library's.&lt;/p&gt;

&lt;p&gt;The trade-off is that the component's &lt;code&gt;.razor&lt;/code&gt; file becomes a thin dispatcher:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@if (VariantRegistry.GetTemplate(GetType(), Variant, this) is RenderFragment template)
{
    @template
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first this felt strange —the component doesn't own its own markup— but it unlocks something no class-based component library offers: &lt;strong&gt;open variants&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I'm not entirely sure whether it's viable in static SSR contexts — if anyone wants to try... :)&lt;br&gt;
But hey, it's out there.&lt;/p&gt;
&lt;h2&gt;
  
  
  Lesson 4: The build pipeline and its dependencies must not leak to consumers
&lt;/h2&gt;

&lt;p&gt;BlazOrbit's CSS bundle is generated by a custom build tool I created ad-hoc: &lt;a href="https://www.nuget.org/packages/CdCSharp.BuildTools" rel="noopener noreferrer"&gt;&lt;code&gt;CdCSharp.BuildTools&lt;/code&gt;&lt;/a&gt;. To be honest it's somewhat tightly coupled to how BlazOrbit uses it, but hey — it's open source and it's out there. It's attribute-based and lets you generate files at build time and run Node sequences (npm install, tsc, vite), all driven by what it generates itself.&lt;/p&gt;

&lt;p&gt;With that I transpile TypeScript modules (I didn't want to give it up), generate the bundles. And everything cleans up when you Clean the project.&lt;/p&gt;

&lt;p&gt;It's transparent to the consumer — no delegation to Node, npm, vite or any equivalent. And at the same time it offers all those capabilities during development.&lt;/p&gt;

&lt;p&gt;I split the pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Maintainer build&lt;/strong&gt; (&lt;code&gt;BlazOrbit.Dev.targets&lt;/code&gt;): runs BuildTools, regenerates assets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consumer build&lt;/strong&gt; (&lt;code&gt;BlazOrbit.targets&lt;/code&gt; inside the &lt;code&gt;.nupkg&lt;/code&gt;): does nothing. The pre-packaged CSS and JS are distributed as &lt;code&gt;staticwebassets&lt;/code&gt; inside the package. The Razor SDK serves them automatically.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Lesson 6: Multi-targeting is not free
&lt;/h2&gt;

&lt;p&gt;Supporting .NET 8 and .NET 10 from the same source code sounds simple: just add &lt;code&gt;&amp;lt;TargetFrameworks&amp;gt;net8.0;net10.0&amp;lt;/TargetFrameworks&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Reality is messier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Razor compiler behaves differently across versions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Microsoft.AspNetCore.Components.Web&lt;/code&gt; has breaking changes in parameter validation.&lt;/li&gt;
&lt;li&gt;Build tools run on &lt;code&gt;net10.0&lt;/code&gt; but must generate assets that work on both.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The worst bug I hit was a race condition in multi-targeting builds. The &lt;code&gt;BeforeBuild&lt;/code&gt; target that invokes BuildTools was running in both the outer build and the inner ones simultaneously, causing file lock conflicts on &lt;code&gt;package.json&lt;/code&gt;. The fix was a condition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;Condition="'$(IsCrossTargetingBuild)' == 'true' OR '$(TargetFrameworks)' == ''"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures BuildTools runs &lt;strong&gt;once per outer build&lt;/strong&gt;, not once per TFM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where we are now
&lt;/h2&gt;

&lt;p&gt;BlazOrbit is at &lt;code&gt;1.0.0-preview.N&lt;/code&gt;. The architecture is stable. The API surface almost too. But still open to changes for now. I'm collecting feedback, adjusting things, and adding a few things to the MVP.&lt;br&gt;
Also waiting for contributors (ahem...) — if any brave soul with a taste for code feels like it, you know where to find us.&lt;/p&gt;

&lt;p&gt;And if you're building a component library, I hope these lessons save you a few weeks of experimentation. If you're a Blazor developer, that is.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;BlazOrbit is open source (MIT). Issues and discussions are open. Any kind of feedback is welcome.&lt;/em&gt;&lt;br&gt;
Give it a try and let me know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blazorbit.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;Website&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/BlazOrbit/BlazOrbit" rel="noopener noreferrer"&gt;&lt;strong&gt;Source code&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nuget.org/packages/BlazOrbit/" rel="noopener noreferrer"&gt;&lt;strong&gt;NuGet package&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://discord.gg/P2kNnT4E" rel="noopener noreferrer"&gt;&lt;strong&gt;Discord Community&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Other topics I may cover in a future post.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CSS standards audit tests across three domains: DOM-emitted, CSS-declared, and library constants&lt;/li&gt;
&lt;li&gt;Integration vs. unit tests for component libraries. Spoiler: Integration testing across two scenarios: Server and Wasm&lt;/li&gt;
&lt;li&gt;Extending some native Blazor components vs. full customization&lt;/li&gt;
&lt;li&gt;Decision: The official documentation resides in the source code.&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>architecture</category>
      <category>dotnet</category>
      <category>blazor</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
