# NeoUI -- Theming NeoUI is 100% compatible with shadcn/ui themes. NeoUI v4.0.0 ships Theme v2: 10 base colors x 17 primary accents = 170 combinations, plus StyleVariant, RadiusPreset, FontPreset, MenuColor, MenuAccent, and ThemePreset bundling all dimensions atomically. ## How Theming Works All NeoUI components use CSS custom properties (variables) in OKLCH color format for colors, spacing, and radius. Switching a theme means swapping variable definitions -- no recompiling, no class changes. ## AppProvider (Required in v4.0.0) `AppProvider` must wrap everything in `MainLayout.razor`, including all portal hosts. It (1) initializes ThemeService and restores the persisted theme from localStorage, and (2) broadcasts the active StyleVariant as a CascadingValue. Components outside AppProvider receive StyleVariant.Default permanently. ```razor @* MainLayout.razor *@ @Body ``` ## CSS Load Order (v4.0.0) Place in `App.razor` in this exact order: ```html ``` ## Key CSS Variables ```css :root { /* Surfaces */ --background: /* page background */; --foreground: /* body text */; --card: /* card background */; --card-foreground: /* card text */; --popover: /* popover/dropdown background */; --popover-foreground: /* popover text */; /* Brand */ --primary: /* primary brand color */; --primary-foreground: /* text on primary */; --secondary: /* secondary surface */; --secondary-foreground: /* text on secondary */; --muted: /* muted background */; --muted-foreground: /* muted text */; --accent: /* hover/accent surface */; --accent-foreground: /* text on accent */; --destructive: /* error/danger color */; --destructive-foreground: /* text on destructive */; /* Structure */ --border: /* border color */; --ring: /* focus ring color */; --radius: /* base border radius */; /* Sidebar */ --sidebar: /* sidebar background */; --sidebar-foreground: /* sidebar text */; --sidebar-primary: /* sidebar active item */; --sidebar-accent: /* sidebar hover */; --sidebar-border: /* sidebar border */; /* Charts */ --chart-1 ... --chart-5: /* chart color palette */; /* Extended tokens (v4.0.0) */ --surface: /* slightly elevated layer between background and card */; --surface-foreground: /* text on surface */; --code: /* code block background */; --code-foreground: /* code block text */; --code-highlight: /* highlighted line background in code blocks */; --code-number: /* line numbers and dimmed code text */; --selection: /* text selection highlight color */; --selection-foreground: /* text color on selection highlight */; /* Alerts */ --alert-info, --alert-info-foreground, --alert-info-bg; --alert-warning, --alert-warning-foreground, --alert-warning-bg; --alert-success, --alert-success-foreground, --alert-success-bg; --alert-danger, --alert-danger-foreground, --alert-danger-bg; /* Typography */ --font-sans: /* body font stack */; --font-heading: /* heading font stack; falls back to --font-sans */; /* Radius scale (proportional, safe at --radius: 0rem) */ --radius-xs: /* x0.4 */; --radius-sm: /* x0.6 */; --radius-md: /* x0.8 */; --radius-lg: /* x1.0 (base) */; --radius-xl: /* x1.4 */; --radius-2xl: /* x1.8 */; --radius-3xl: /* x2.2 */; --radius-4xl: /* x2.6 */; /* Shadows (set by StyleVariant; Luma uses soft diffuse OKLCH shadows) */ --shadow-xs --shadow-sm --shadow-md --shadow-lg --shadow-xl --shadow-2xl; } ``` ## Dark Mode Dark mode variables are defined in each theme file under `.dark` / `[data-theme="dark"]`. Use the built-in toggle: ```razor ``` Or `ThemeSwitcher` for full palette + style switching: ```razor ``` Register the theme service: ```csharp builder.Services.AddNeoUIComponents(); // includes ThemeService ``` ## StyleVariant Enum Controls `--radius`, `--spacing-scale`, and shadow values. Broadcast as CascadingValue from AppProvider. | Variant | --radius | --spacing-scale | Character | |---------|------------|-----------------|----------------------------------| | Default | unchanged | 1 | Backward-compatible (no file) | | Vega | 0.625rem | 1 | Professional, balanced | | Nova | 0.375rem | 0.85 | Compact, dashboard/admin | | Maia | 1rem | 1.15 | Spacious, consumer-friendly | | Lyra | 0rem | 1 | Sharp/boxy, developer tooling | | Mira | 0.25rem | 0.7 | Ultra-dense, data-heavy | | Luma | 0.75rem | 1 | Glassmorphism, modern SaaS | CSS: `_content/NeoUI.Blazor/css/themes/styles/{vega,nova,maia,lyra,mira,luma}.css` Luma extras: soft diffuse shadows, semi-transparent inputs via `color-mix`, stronger overlay blur (`--blur-sm: 4px` on `[data-slot="overlay"]`). ```csharp await ThemeService.SetStyleVariantAsync(StyleVariant.Nova); ``` ## RadiusPreset Enum Independent named radius overrides; takes precedence over StyleVariant in the CSS cascade. | Preset | Value | |--------|----------------------------| | None | 0rem | | Small | 0.45rem | | Medium | 0.625rem (default, no file)| | Large | 0.875rem | CSS: `_content/NeoUI.Blazor/css/themes/radius/{none,small,large}.css` ```csharp await ThemeService.SetRadiusPresetAsync(RadiusPreset.Large); ``` ## 7-Step Proportional Radius Scale All scale steps derive from `--radius`; safe at `--radius: 0rem` (Lyra style). | Variable | Multiplier | |-------------|------------| | --radius-xs | x0.4 | | --radius-sm | x0.6 | | --radius-md | x0.8 | | --radius-lg | x1.0 (base)| | --radius-xl | x1.4 | | --radius-2xl| x1.8 | | --radius-4xl| x2.6 | ## FontPreset Enum 6 curated font pairings setting `--font-sans` and `--font-heading`. | Preset | Description | |-------------|-------------------------------| | System | system-ui stack (default, no file) | | Inter | Inter | | Geist | Geist | | CalSans | CalSans | | DmSans | DM Sans | | PlusJakarta | Plus Jakarta Sans | CSS: `_content/NeoUI.Blazor/css/themes/fonts/{inter,geist,calsans,dmsans,plusjakarta}.css` **Note:** Font CSS files set CSS variables only. Load the actual font face files separately (e.g. Google Fonts `` tag or local @font-face declarations). ```csharp await ThemeService.SetFontPresetAsync(FontPreset.Inter); ``` ## --font-heading Variable Falls back to `--font-sans` when not explicitly set. Font presets assign it independently to enable distinct heading typography. ## MenuColor Enum Controls background treatment of all floating surfaces: Popover, DropdownMenu, Select, Combobox. | Value | Treatment | |----------------------|-------------------------------------------------------------| | Default | Solid surface | | Inverted | Dark surface (light mode only) | | DefaultTranslucent | 50% opacity + blur(18px) + saturate(150%) | | InvertedTranslucent | Dark + 70% opacity + blur (light mode only) | ```csharp await ThemeService.SetMenuColorAsync(MenuColor.DefaultTranslucent); ``` ## MenuAccent Enum Controls hover intensity on menu items. | Value | Color | |--------|------------------------------------------| | Subtle | --accent (default) | | Bold | --primary (high-contrast brand hover) | ```csharp await ThemeService.SetMenuAccentAsync(MenuAccent.Bold); ``` ## BaseColor Enum (10 Total) v4.0.0 adds 5 new chromatic neutrals. | Color | Character | |---------|-----------------------------------| | Zinc | Neutral gray (default) | | Slate | Cool blue-gray | | Gray | Neutral gray | | Neutral | Warm gray | | Stone | Warm brownish | | Luma | Blue-indigo tinted (new in v4) | | Mist | Cool blue-gray (new in v4) | | Mauve | Warm purple-gray (new in v4) | | Taupe | Warm brownish-gray (new in v4) | | Olive | Muted green-gray (new in v4) | CSS: `_content/NeoUI.Blazor/css/themes/base/{luma,mist,mauve,taupe,olive}.css` Total combinations: 10 base x 17 primary = **170** ```csharp await ThemeService.SetBaseColorAsync(BaseColor.Luma); ``` ## ThemePreset Record Bundles all 8 theme dimensions atomically: BaseColor, PrimaryColor, StyleVariant, RadiusPreset, FontPreset, MenuAccent, MenuColor, and Name. ### Built-in Presets | Preset | Base | Style | Radius | Font | |---------|-------|---------|--------|-------------| | Default | Zinc | Default | Medium | System | | Luma | Zinc | Luma | Medium | Inter | | Nova | Zinc | Nova | Small | Geist | | Maia | Mauve | Maia | Large | PlusJakarta | | Lyra | Slate | Lyra | None | Geist | ```csharp // Apply a built-in preset await ThemeService.ApplyPresetAsync(ThemePreset.Luma); // Create and apply a custom preset var custom = new ThemePreset( Name: "Glass Dashboard", BaseColor: BaseColor.Luma, PrimaryColor: PrimaryColor.Blue, StyleVariant: StyleVariant.Luma, RadiusPreset: RadiusPreset.Medium, FontPreset: FontPreset.Inter, MenuAccent: MenuAccent.Bold, MenuColor: MenuColor.DefaultTranslucent ); await ThemeService.ApplyPresetAsync(custom); ``` ## ThemeService API ```razor @inject IThemeService ThemeService ``` ### Properties | Property | Type | |-----------------------|----------------| | CurrentBaseColor | BaseColor | | CurrentPrimaryColor | PrimaryColor | | CurrentStyleVariant | StyleVariant | | CurrentRadiusPreset | RadiusPreset | | CurrentFontPreset | FontPreset | | CurrentMenuAccent | MenuAccent | | CurrentMenuColor | MenuColor | ### Methods | Method | Description | |-------------------------------------|------------------------------------------| | SetBaseColorAsync(BaseColor) | Switch base palette | | SetPrimaryColorAsync(PrimaryColor) | Switch primary accent | | SetStyleVariantAsync(StyleVariant) | Switch style variant | | SetRadiusPresetAsync(RadiusPreset) | Switch radius preset | | SetFontPresetAsync(FontPreset) | Switch font preset | | SetMenuAccentAsync(MenuAccent) | Switch menu item hover intensity | | SetMenuColorAsync(MenuColor) | Switch floating surface treatment | | ApplyPresetAsync(ThemePreset) | Apply all 8 dimensions atomically | | SetTheme(base, primary) | Legacy method -- still works | ### Runtime Switching Examples ```razor @inject IThemeService ThemeService ``` ## ThemeSwitcher Component ```razor ``` | Parameter | Type | Default | Description | |--------------------|---------------------|---------|-------------------------------------------------------| | ShowStyles | bool | false | Adds Styles tab (variant, radius, font, menu options) | | Strategy | PositioningStrategy | Fixed | Popover positioning strategy | | ZIndex | int | 60 | z-index of the switcher popover | | TriggerClass | string? | null | CSS classes on the trigger button | | PopoverContentClass| string? | null | CSS classes on the popover content | | Align | PopoverAlign | End | Popover alignment | Trigger button shows a two-tone swatch: current base color (fill) + current primary (border). With `ShowStyles="true"`: Colors tab (base + primary selectors) + Styles tab (variant, radius, font, menu accent, menu color). ## Custom Theme (OKLCH Format) NeoUI v4 uses OKLCH color format internally: ```css /* wwwroot/css/my-theme.css */ :root { --background: oklch(1 0 0); --foreground: oklch(0.145 0 0); --primary: oklch(0.623 0.214 259.8); --primary-foreground: oklch(0.985 0 0); --secondary: oklch(0.96 0 0); --muted: oklch(0.96 0 0); --accent: oklch(0.96 0 0); --destructive: oklch(0.577 0.245 27.3); --border: oklch(0.92 0 0); --ring: oklch(0.623 0.214 259.8); --radius: 0.625rem; } .dark { --background: oklch(0.145 0 0); --foreground: oklch(0.985 0 0); /* ... */ } ``` ## shadcn/ui Compatibility NeoUI is 100% compatible with shadcn/ui themes. Use any theme from https://ui.shadcn.com/themes or https://tweakcn.com directly. shadcn/ui themes use HSL format; NeoUI's own files use OKLCH. Both formats work -- CSS custom properties accept either. ## Tailwind v4 Integration NeoUI's pre-built CSS works without Tailwind. If using Tailwind v4 for custom styles, you **must** add `@theme inline` to your Tailwind build input. Without it, Tailwind v4 emits hardcoded `oklch()` values that override NeoUI's runtime CSS variable changes: ```css /* app.css */ @import "tailwindcss"; @theme inline { /* your customizations */ } ```