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

Our Philosophy

BlazorNova is built around a small set of design principles that make components predictable, composable, and theme-adaptive — without requiring you to wire up colours or hover states manually.

The Surface System

Every piece of UI lives on a surface — a named palette that defines background, text, input, and semantic colours for that visual context. 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, 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 (Primary1 = normal, Primary2 = hover), not to nest one branded surface inside another.

The Relative Surface Pattern

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.

This means hover states are written the same way everywhere — regardless of which surface a component happens to be placed on:

csharp
// Hover and selection — use EmphasisBg (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 (softer than OnBg)
Bn.border.border_solid.border_Border

// Reserve rel1 for true depth nesting (child above parent)

A button, a list item, a card — all use the same hover expression. The resolved colour adapts automatically to the containing surface at render time. This is the core reason branded surfaces have a 1/2 pair: so there is always a well-defined rel1 for hover, matching the same contract as neutral surfaces.

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.

Color Palette Design

Each surface palette defines a focused set of semantic colour tokens:

  • Bg / OnBg — container background and its content colour
  • AltBg — subtle alternate background (e.g. alternating table rows)
  • EmphasisBg — emphasis for hover and selection states
  • Border — soft mid-tone border colour
  • Input / OnInput — input field background and text
  • Error, ErrorContainer, OnErrorContainer
  • Warning, WarningContainer, OnWarningContainer
  • Success, SuccessContainer, OnSuccessContainer

There is no per-surface Accent token. A "primary action" colour always comes from SurfacePrimary1-Bg; secondary from SurfaceSecondary1-Bg. This makes intent explicit: a button chooses its visual weight by declaring its variant (ButtonVariant.Primary), not by inheriting whatever accent the parent surface happens to define.

Semantic colours (Error, Warning, Success) remain per-surface because they need to be readable on top of each specific surface background. Brand colours do not have this dependency — they are always the same, regardless of context.

Style Overrides with BnModifier

Every component exposes one or more BnModifier parameters for the parts of its layout that are worth customising. A modifier wraps a styling function and is applied on top of the component's defaults.

razor
@* Caller overrides the card container *@
<BnCard ContainerModifier="@BnModifier.Create(x => x.rounded_xl.shadow_lg)" />

@* Inside the component, defaults merge with the caller's modifier *@
public BlazorNova ContainerBn =>
    BnModifier.Create(x => x.rounded_md.p_4)
              .OverrideWith(ContainerModifier);

Modifiers are additive — they run after the defaults, so you can extend, narrow, or replace specific properties without rewriting the entire style chain. Components compose their defaults with BnModifier.Create(...).OverrideWith(ContainerModifier) so the caller always wins.

The Fluent CSS API

BlazorNova.New starts a fresh CSS class builder. Chain utility properties and methods to compose the class string for any element. Conditional classes, responsive breakpoints, and pseudo-state variants (Hover, Focus, Disabled, etc.) are all first-class:

csharp
// Static layout — no surface needed
BlazorNova.New.flex.gap_4.items_center.p_3

// Conditional and responsive
BlazorNova.New
    .w_full
    .Md(x => x.w_[480px])
    .If(() => isActive, x => x.font_bold)
    .Hover(x => x.opacity_80)

// Surface-aware (requires SetSurface or NextBn)
NextBn.bg_Bg.text_OnBg.border_Border
     .Hover(h => h.bg_EmphasisBg)

Surface-aware styles are produced when the builder has a surface set via SetSurface() or via NextBn (which sets the surface automatically). Plain BlazorNova.New is used for static, surface-independent styles such as layout utilities.