The fluent API deduplicates CSS properties using a "last wins" rule within each
prefix scope. This section explains how that interacts with pseudo-class prefixes
like .Odd() and .Hover(), and how .If().Else()
gives you full control when conditional styles conflict with prefix-scoped styles.
When two utilities target the same CSS property in the same prefix scope, the later
one replaces the earlier one. Internally the builder stores classes in a dictionary
keyed by prefix + cssProperty, so the second assignment overwrites the first.
// Both target background-color with no prefix — red wins:
BlazorNova.New.bg_black.bg_red
// Output: "bg-red"
// Same rule applies within a prefix scope:
BlazorNova.New.Hover(h => h.bg_black.bg_red)
// Output: "hover:bg-red"
Each pseudo-class prefix (.Odd(), .Even(),
.Hover(), .Focus(), etc.) creates its own dictionary key
space. A non-prefixed bg_red and an odd:bg_blue target
the same CSS property but live under different keys, so both are emitted.
// Different prefix scopes — both are emitted:
BlazorNova.New.bg_red.Odd(o => o.bg_blue).Hover(h => h.bg_green)
// Output: "bg-red odd:bg-blue hover:bg-green"
// Dictionary keys:
// "background-color" → "bg-red"
// "odd:background-color" → "odd:bg-blue"
// "hover:background-color" → "hover:bg-green"This is normally the correct behaviour — you want a base colour and a hover colour to coexist. The problem arises when a runtime condition (like row selection) needs to override a prefix-scoped style.
When two pseudo-class selectors have equal specificity (e.g. :nth-child(odd)
and :hover both add one pseudo-class), the rule that appears
later in the stylesheet wins. BlazorNova controls this ordering with an
explicit priority system in the CSS generator. Groups are sorted by breakpoint pixel
value first, then by pseudo-class priority, then alphabetically as a tiebreaker.
// Pseudo-class priority (lower = earlier in CSS, higher = wins cascade)
//
// Structural selectors Interactive states
// first 10 hover 30
// last 11 focus 40
// odd 12 focus-within 41
// even 13 focus-visible 42
// active 50
// Form / input states
// disabled 20
// enabled 21
// checked 22
// readonly 23
// required 24
//
// Sort: Order (breakpoint px) → Priority → Alphabetical
//
// Result in generated CSS:
// .odd\:bg-red:nth-child(odd) { ... } /* priority 12 — appears first */
// .hover\:bg-blue:hover { ... } /* priority 30 — appears later */
// .active\:bg-green:active { ... } /* priority 50 — appears last */
//
// Same specificity → later rule wins → hover beats odd.
This ordering was introduced because an earlier alphabetical sort placed
hover (h) before odd (o) in the output, causing hover
highlights to be overridden by odd/even row colours. The explicit priority ensures
interactive states always appear after structural selectors in the generated CSS,
so a :hover rule correctly overrides :nth-child(odd) when
both match the same element.
Consider alternating row colours with a conditional selection highlight:
// Alternating rows with conditional selection — BROKEN:
NextBn.bg_Bg.text_OnBg
.Odd(o => o.bg_AltBg)
.Hover(h => h.bg_EmphasisBg)
.If(() => isSelected, x => x.bg_EmphasisBg)
// When isSelected = true, output includes BOTH:
// bg-EmphasisBg (from If)
// odd:bg-AltBg (from Odd — still present!)
//
// :nth-child(odd) has higher specificity → odd colour wins.
When isSelected is true, both bg_rel2_Bg and
odd:bg_rel1_Bg are emitted. In the generated CSS,
:nth-child(odd) has higher specificity than a plain class, so the odd
colour always wins — the selection highlight never appears.
.If() returns a BlazorNovaIfConditional that exposes an
.Else() method. When the condition is true only the If branch is applied;
when false only the Else branch runs. Because the branches are mutually exclusive,
conflicting classes are never on the element at the same time.
// Alternating rows with conditional selection — FIXED:
NextBn
.Hover(h => h.bg_EmphasisBg)
.If(() => isSelected, x => x.bg_EmphasisBg)
.Else(x => x.bg_Bg.text_OnBg.Odd(o => o.bg_AltBg))
// When isSelected = true:
// hover:bg-EmphasisBg bg-EmphasisBg
// (no Odd classes — no specificity conflict)
// When isSelected = false:
// hover:bg-EmphasisBg bg-Bg text-OnBg odd:bg-AltBg
Move any prefix-scoped styles that would conflict with your conditional into the
.Else() branch. Styles that should apply in both states (like
.Hover()) stay outside the If/Else pair.
.Else() returns BlazorNova, so you can continue the
fluent chain normally. Each .If() can optionally have its own
.Else(). Without .Else(), .If() behaves
exactly as before — the modifier is applied when true, skipped when false.
NextBn
.Hover(h => h.bg_EmphasisBg) // always applied
.If(() => isSelected, x => x.bg_EmphasisBg) // branch A
.Else(x => x.bg_Bg.Odd(o => o.bg_AltBg)) // branch B
.If(() => isEditable, x => x.cursor_pointer) // independent If (no Else)
.border.border_solid.border_Border // always applied