Every piece of UI in BlazorNova lives on a surface — a named palette that defines the background, text, input, and semantic colours for that visual region. Surfaces are the mechanism by which the entire component tree stays cohesive without any manual colour wiring.
Wrap any region in a <BnSurface> and all descendant components
automatically inherit the appropriate palette depth. Neutral surfaces form a nesting
hierarchy:
Surface0 → Surface1 → Surface2 → Surface3 → Surface1…
Each step represents one level of visual elevation — a page body, then a panel, then a card, then a row. You never set the depth manually; it falls out of the structure of your markup.
@* Page lives on Surface0 implicitly *@
<BnSurface> @* Surface1 — panel *@
<BnSurface> @* Surface2 — card *@
<BnSurface> @* Surface3 — row *@
<BnTextBox ... />
</BnSurface>
</BnSurface>
</BnSurface>
@* Force a branded context anywhere in the tree *@
<BnSurface SurfacePalette="@_engine.ThemePalette.SurfacePrimary1">
<BnButton Label="Primary Action" />
</BnSurface>
Branded surfaces (SurfacePrimary1/2, SurfaceSecondary1/2,
SurfaceTertiary1/2) represent visual intent rather than depth.
A region wrapped in SurfacePrimary1 signals a primary-branded context —
a hero section, a highlighted callout, or a primary action button.
The 1/2 pairs for branded surfaces exist to provide normal and hover/active
states, not to nest one branded surface inside another.
Each surface palette includes three tokens for visual variation within that surface, without consuming additional surface levels:
EmphasisBg — hover and selection statesAltBg — subtle alternating backgrounds (e.g. table row striping)Border — soft border colour (much lighter than OnBg)// Hover — use EmphasisBg (stays within the same surface)
Bn.bg_Bg.text_OnBg
.Hover(h => h.bg_EmphasisBg)
// Alternating rows — use AltBg
Bn.bg_Bg.text_OnBg
.Odd(o => o.bg_AltBg)
// Borders — use the Border token
Bn.border.border_solid.border_BorderThis pattern keeps surface levels for actual depth nesting (page → panel → card) and uses the palette tokens for interactive states and visual rhythm.
The rel1 suffix means "one level deeper than my current surface."
For a component sitting on Surface1, bg_rel1_Bg resolves to
Surface2-Bg. For a component on SurfacePrimary1,
bg_rel1_Bg resolves to SurfacePrimary2-Bg.
Use rel1 when you need true depth nesting — a child region that
sits visually above its parent (e.g. a grid header that uses bg_rel1_Bg to sit
one level above the grid body). Do not use rel1 for hover or
selection states — use bg_EmphasisBg instead.
// rel1 = genuine depth nesting (child sits above parent)
// Grid header one level above the grid body:
NextBn.bg_rel1_Bg.text_rel1_OnBg
// rel1 resolves based on the current surface:
// On Surface1 → rel1 = Surface2-Bg
// On Primary1 → rel1 = Primary2-Bg
Components do not know — and should not care — which surface they are placed on.
They receive the current surface via a cascading parameter and use NextBn
to build their styles relative to it. NextBn always starts one level
deeper than the parent surface, so a component's background automatically sits above
its container without any explicit configuration.
@* Inside a component — never references a specific surface name *@
<div class="@NextBn.bg_Bg.text_OnBg.rounded_md.p_3
.Hover(h => h.bg_EmphasisBg)">
@ChildContent
</div>
The same component placed inside Surface1, SurfacePrimary1,
or SurfaceSecondary1 will always look correct — background, text, and
hover colours all resolve from the local palette context.
Convention: any component that renders its own surface background and
contains other components must wrap those children in a <BnSurface>.
The cascade is the only mechanism child components have to discover the surface shift.
A component that uses NextBn.bg_Bg for its own styling without cascading
a new BnSurface will cause its children to compute the wrong depth.