BlazorNova's palette is a structured set of surfaces rather than a flat list of colour tokens. Each surface defines exactly the colours needed for the UI that lives on it, and the system distinguishes clearly between neutral depth and branded intent.
Each surface palette defines a focused set of semantic colour tokens:
Bg / OnBg — container background and its foreground colourAltBg — subtle alternate background for visual variation within a surface (e.g. alternating table rows)EmphasisBg — emphasis background for interactive states like hover and selectionBorder — soft mid-tone border colour, much less harsh than OnBgInput / OnInput — input field background and textError, ErrorContainer, OnErrorContainerWarning, WarningContainer, OnWarningContainerSuccess, SuccessContainer, OnSuccessContainerAltBg, EmphasisBg, and Border solve a common problem:
components that previously consumed extra surface levels for visual differentiation within a
single surface. A grid no longer needs to burn Surface1 for odd rows and Surface2 for hover —
it uses bg_AltBg for alternating rows and bg_EmphasisBg for
hover/selected states, all within the same surface depth.
Semantic colours (Error, Warning, Success) are per-surface because they need to be readable on top of each specific surface background. A red that works on a white card may not read clearly on a dark navy panel.
There is no Accent token on individual surfaces. A "primary action" colour
always comes from SurfacePrimary1-Bg; secondary from
SurfaceSecondary1-Bg. Brand colours do not have a dependency on the
containing surface — they are always the same, regardless of context.
This makes intent explicit: a button declares its visual weight by choosing a
ButtonVariant (Primary, Secondary,
Tertiary), not by inheriting whatever accent the parent surface
happens to define.
// A button picks its palette explicitly — no ambient accent needed.
<BnButton Label="Save" Variant="ButtonVariant.Primary" />
<BnButton Label="Cancel" Variant="ButtonVariant.Secondary" />
<BnButton Label="More" Variant="ButtonVariant.Surface" />
Each branded palette has two levels — Primary1 and Primary2
— not for nesting one branded surface inside another, but to provide the
normal and hover/active states required by the rel1 hover mechanism.
Primary1 is the resting state. Primary2 is the hover shade.
Because neutral surfaces follow the same rel1 contract
(Surface1 → Surface2), all hover expressions are written identically
regardless of which palette family a component sits on.
// For most components, hover uses EmphasisBg — stays within the same surface:
Bn.SetSurface(SurfacePrimary1).bg_Bg.text_OnBg
.Hover(h => h.bg_EmphasisBg)
// EmphasisBg is a pre-defined hover shade within each palette.
// This avoids consuming the next surface level just for hover.
// The rel1 pattern still exists for true depth nesting:
// SurfacePrimary1-Bg → resting background (e.g. Violet-600)
// SurfacePrimary2-Bg → next depth level (e.g. Violet-500)
// Use rel1 when a child region genuinely needs a deeper surface.
Override LightPalette and/or DarkPalette in your
BlazorNovaConfig subclass to supply your brand colours. The generated CSS
emits one set of CSS custom properties per palette entry, so the entire component tree
— including any installed BlazorNova component libraries — picks up your colours
automatically.
public class MyAppConfig : BlazorNovaConfig
{
public override string RelativeOutputDirectory { get; } = "/wwwroot/generated";
public override ThemePalette LightPalette { get; } = new ThemePalette
{
// Override specific entries; unset entries use BlazorNova defaults.
SurfacePrimary1 = new SurfacePrimary1
{
Bg = Color.FromArgb(255, 37, 99, 235), // Blue-600
OnBg = Color.White
},
SurfacePrimary2 = new SurfacePrimary2
{
Bg = Color.FromArgb(255, 59, 130, 246), // Blue-500 (hover)
OnBg = Color.White
}
};
}