BlazorNova BlazorNova
BlazorNova BlazorNova
☰
☰ Menu
Getting Started
Installation
Project Setup
Source Generator
Core Concepts
Surface System
Color Palette
Fluent CSS API
Style Overrides
CSS Precedence
BlazorNova Classes
Layout
Flexbox & Grid
Spacing
Sizing
Typography
Backgrounds
Borders
Effects
Filters
Transitions & Transforms
Interactivity
Components
Forms
BnEditForm
FormState
Monitors
MenuBar
Horizontal
Vertical
Tabs
BnTab
Icons
Icon Gallery
Buttons
BnButton
Layout
BnStackPanel
BnDrawer
Grid
BnGrid
BnPagedGrid
Navigation
BnNavigator
Carousel
BnCarousel
Charts
BnChart
PDF Viewer
BnPdfViewer
Meet
BnVideoCall

The Surface System

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.

Surface Cascade

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.

razor
@* 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.

Hover, Selection, and Visual Variation

Each surface palette includes three tokens for visual variation within that surface, without consuming additional surface levels:

  • EmphasisBg — hover and selection states
  • AltBg — subtle alternating backgrounds (e.g. table row striping)
  • Border — soft border colour (much lighter than OnBg)
csharp
// 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_Border

This pattern keeps surface levels for actual depth nesting (page → panel → card) and uses the palette tokens for interactive states and visual rhythm.

The Relative Surface Pattern (rel1)

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.

csharp
// 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 Are Surface-Agnostic

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.

razor
@* 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.