Log in

Theming

NeoUI is 100% compatible with shadcn/ui themes. Use any theme from the shadcn/ui gallery, generate one with tweakcn, write your own CSS variables, or use Theme v2 to unlock style variants, radius presets, and font presets.

How Theming Works

All NeoUI components use CSS custom properties (variables) for their colors, spacing, and radius. Switching a theme is as simple as swapping out those variable definitions — no recompiling, no class changes, no JavaScript framework needed.

The included theme files (_content/NeoUI.Blazor/css/themes/) ship ten base palettes and seventeen primary accent colors, giving you 170 ready-to-use combinations out of the box — plus optional style variants, radius presets, and font presets introduced in Theme v2.

Pre-Built Theme System

Include theme CSS files in App.razor after components.css:

RZApp.razor
<!-- 1. Core styles — always first -->
<link rel="stylesheet" href="@Assets["_content/NeoUI.Blazor/components.css"]" />

<!-- 2. Base color (pick one of 10) -->
<link rel="stylesheet" href="@Assets["_content/NeoUI.Blazor/css/themes/base/zinc.css"]" />

<!-- 3. Primary accent color (pick one of 17) -->
<link rel="stylesheet" href="@Assets["_content/NeoUI.Blazor/css/themes/primary/blue.css"]" />

<!-- 4. Style variant — optional (vega · nova · maia · lyra · mira · luma) -->
<!-- <link rel="stylesheet" href="@Assets["_content/NeoUI.Blazor/css/themes/styles/nova.css"]" /> -->

<!-- 5. Radius preset — optional (none · small · large) -->
<!-- <link rel="stylesheet" href="@Assets["_content/NeoUI.Blazor/css/themes/radius/small.css"]" /> -->

<!-- 6. Font preset — optional (inter · geist · calsans · dmsans · plusjakarta) -->
<!-- <link rel="stylesheet" href="@Assets["_content/NeoUI.Blazor/css/themes/fonts/geist.css"]" /> -->

<!-- 7. Theme init script (prevents FOUC — must be last) -->
<script src="@Assets["_content/NeoUI.Blazor/js/theme.js"]"></script>

Base colors (10)

  • zinc · slate · gray · neutral · stone
  • + new in v4: luma · mist · mauve · taupe · olive

Primary accent colors (17)

  • red · rose · orange · amber · yellow · lime
  • green · emerald · teal · cyan · sky · blue
  • indigo · violet · purple · fuchsia · pink

Theme v2 — Style Variants & Presets

v4.0.0

Theme v2, introduced in v4.0.0, adds style variants, radius presets, font presets, and sidebar color/accent options layered on top of the base + primary color system — giving you far more expressive control without writing any custom CSS. 10 base colors × 17 primary colors = 170 combinations, plus 7 style variants, 4 radius options, and 6 font options.

AppProvider — Required Wrapper

Theme v2 distributes the active StyleVariant to every component via a CascadingValue. Wrap your main layout content with AppProvider — components outside it permanently receive StyleVariant.Default.

RZMainLayout.razor
@inherits LayoutComponentBase

@* AppProvider is required for Theme v2 — provides StyleVariant CascadingValue to all components.
   Portal hosts go INSIDE AppProvider so overlays inherit the active style variant. *@
<AppProvider>
    <div class="min-h-screen bg-background">
        @Body
    </div>

    @* Portal hosts inside AppProvider so overlays inherit the active style variant *@
    <ToastViewport />
    <DialogHost />
    <ContainerPortalHost />
    <OverlayPortalHost />
</AppProvider>

StyleVariant Enum

Six visual treatment options for components. The CSS file for each variant must be loaded in App.razor.

Value Description
StyleVariant.Default Classic shadcn/ui-aligned look — clean and neutral.
StyleVariant.Vega Soft, warm treatment with subtle gradients.
StyleVariant.Nova Crisp, modern variant with strong contrast.
StyleVariant.Maia Rounded, friendly aesthetic inspired by soft UI.
StyleVariant.Lyra Minimal, editorial style with open whitespace.
StyleVariant.Mira Bold, vivid accents with high-energy presence.
StyleVariant.Luma Luminous, lightweight variant optimized for bright themes.

RadiusPreset Enum

Controls global border-radius. Load a radius CSS file in App.razor to activate.

Value Description
RadiusPreset.None Square corners — radius: 0.
RadiusPreset.Small Slightly rounded — radius: 0.25rem.
RadiusPreset.Medium Default rounded — radius: 0.5rem (matches shadcn/ui default).
RadiusPreset.Large Strongly rounded — radius: 0.75rem.

FontPreset Enum

Sets --font-sans and the new --font-heading CSS variable. Load a font CSS file in App.razor to activate.

Value Description
FontPreset.System System UI font stack — no web font loaded.
FontPreset.Inter Inter — clean and highly legible sans-serif.
FontPreset.Geist Geist — Vercel's modern mono-influenced sans.
FontPreset.CalSans Cal Sans — expressive heading font (sets --font-heading).
FontPreset.DmSans DM Sans — rounded, approachable sans-serif.
FontPreset.PlusJakarta Plus Jakarta Sans — geometric and contemporary.

MenuColor & MenuAccent

Control sidebar/navigation appearance independently of the main component style.

C#
// MenuColor: Default | Inverted | DefaultTranslucent | InvertedTranslucent
// MenuAccent: Subtle | Bold

await ThemeService.SetMenuColor(MenuColor.Inverted);
await ThemeService.SetMenuAccent(MenuAccent.Bold);

Built-in ThemePresets

Apply a full preset (base + primary + style + radius + font) in one call. The CSS for all referenced files must be loaded in App.razor.

C#
// Built-in presets — each bundles base + primary + style + radius + font
// ThemePreset.Default  — Zinc + Default
// ThemePreset.Luma     — Zinc + Luma + Inter
// ThemePreset.Nova     — Zinc + Nova + Small + Geist
// ThemePreset.Maia     — Mauve + Maia + Large + PlusJakarta
// ThemePreset.Lyra     — Slate + Lyra + None + Geist

await ThemeService.ApplyPreset(ThemePreset.Nova);

ThemeService v4 API

RZ
@inject ThemeService ThemeService

@code {
    // Color
    await ThemeService.SetPrimaryColorAsync(PrimaryColor.Violet);
    await ThemeService.SetBaseColorAsync(BaseColor.Slate);

    // Style, radius, font (Theme v2)
    await ThemeService.SetStyleVariant(StyleVariant.Nova);
    await ThemeService.SetRadiusPreset(RadiusPreset.Small);
    await ThemeService.SetFontPreset(FontPreset.Geist);

    // Sidebar / navigation
    await ThemeService.SetMenuColor(MenuColor.Inverted);
    await ThemeService.SetMenuAccent(MenuAccent.Bold);

    // Apply a full preset at once
    await ThemeService.ApplyPreset(ThemePreset.Nova);

    // Read the current theme state
    var current = ThemeService.GetCurrentTheme();
}

CSS Load Order

The cascade order matters — each layer builds on the previous. Load all desired files in App.razor in this exact order:

RZApp.razor
<head>
    <!-- 1. Core component styles (always first) -->
    <link rel="stylesheet" href="@Assets["_content/NeoUI.Blazor/components.css"]" />

    <!-- 2. Base color — pick one of 10 (zinc · slate · gray · neutral · stone · luma · mist · mauve · taupe · olive) -->
    <link rel="stylesheet" href="@Assets["_content/NeoUI.Blazor/css/themes/base/zinc.css"]" />

    <!-- 3. Primary accent — pick one of 17 -->
    <link rel="stylesheet" href="@Assets["_content/NeoUI.Blazor/css/themes/primary/blue.css"]" />

    <!-- 4. Style variant — optional (vega · nova · maia · lyra · mira · luma) -->
    <link rel="stylesheet" href="@Assets["_content/NeoUI.Blazor/css/themes/styles/nova.css"]" />

    <!-- 5. Radius preset — optional (none · small · large) -->
    <link rel="stylesheet" href="@Assets["_content/NeoUI.Blazor/css/themes/radius/small.css"]" />

    <!-- 6. Font preset — optional (inter · geist · calsans · dmsans · plusjakarta) -->
    <link rel="stylesheet" href="@Assets["_content/NeoUI.Blazor/css/themes/fonts/geist.css"]" />

    <!-- 7. Your custom app CSS (after all NeoUI files) -->
    <link rel="stylesheet" href="@Assets["_content/YourApp.Shared/css/app.css"]" />

    <!-- 8. Theme init script — must be last, prevents FOUC -->
    <script src="@Assets["_content/NeoUI.Blazor/js/theme.js"]"></script>
</head>

Dynamic Theme Switching

NeoUI includes a built-in theme switcher that lets users pick their preferred color combination at runtime. Changes apply instantly without a page reload, and the selection is persisted via localStorage.

Add the components to your layout header:

RZ
@* Add to your layout header *@
@* ShowStyles="true" enables the style variant picker panel (Theme v2) *@
<ThemeSwitcher ShowStyles="true" />
<DarkModeToggle />

For programmatic control, inject ThemeService:

RZ
@inject ThemeService ThemeService

<Button @onclick="SetPurple">Switch to Purple</Button>

@code {
    private async Task SetPurple()
        => await ThemeService.SetPrimaryColorAsync(PrimaryColor.Purple);
}

Custom CSS Theme

You can use any theme from ui.shadcn.com/themes or tweakcn.com. Copy the generated CSS and paste it into wwwroot/styles/theme.css:

CSSwwwroot/styles/theme.css
@layer base {
  :root {
    --background: oklch(1 0 0);
    --foreground: oklch(0.145 0 0);
    --card: oklch(1 0 0);
    --card-foreground: oklch(0.145 0 0);
    --primary: oklch(0.205 0 0);
    --primary-foreground: oklch(0.985 0 0);
    --secondary: oklch(0.97 0 0);
    --secondary-foreground: oklch(0.205 0 0);
    --muted: oklch(0.97 0 0);
    --muted-foreground: oklch(0.556 0 0);
    --accent: oklch(0.97 0 0);
    --accent-foreground: oklch(0.205 0 0);
    --destructive: oklch(0.577 0.245 27.325);
    --border: oklch(0.922 0 0);
    --input: oklch(0.922 0 0);
    --ring: oklch(0.708 0 0);
    --radius: 0.5rem;
    --chart-1: oklch(0.646 0.222 41.116);
    --chart-2: oklch(0.6 0.118 184.704);
    --chart-3: oklch(0.398 0.07 227.392);
    --chart-4: oklch(0.828 0.189 84.429);
    --chart-5: oklch(0.769 0.188 70.08);
  }

  .dark {
    --background: oklch(0.145 0 0);
    --foreground: oklch(0.985 0 0);
    --primary: oklch(0.922 0 0);
    --primary-foreground: oklch(0.205 0 0);
    /* ... remaining dark overrides */
  }
}

Then reference it in App.razor at step 7 in the load order — after all NeoUI CSS files but before theme.js:

RZApp.razor
<!-- NeoUI core and theme files first -->
<link rel="stylesheet" href="@Assets["_content/NeoUI.Blazor/components.css"]" />
<link rel="stylesheet" href="@Assets["_content/NeoUI.Blazor/css/themes/base/zinc.css"]" />
<link rel="stylesheet" href="@Assets["_content/NeoUI.Blazor/css/themes/primary/blue.css"]" />

<!-- Your custom theme variables — load AFTER all NeoUI files -->
<link rel="stylesheet" href="styles/theme.css" />

<!-- Theme init last -->
<script src="@Assets["_content/NeoUI.Blazor/js/theme.js"]"></script>

Available CSS Variables

These are the standard shadcn/ui variables NeoUI reads. You can override any of them.

CSS
/* Surfaces */
--background          --foreground
--card                --card-foreground
--popover             --popover-foreground

/* Semantic colors */
--primary             --primary-foreground
--secondary           --secondary-foreground
--muted               --muted-foreground
--accent              --accent-foreground
--destructive         --destructive-foreground

/* Borders & inputs */
--border  --input  --ring

/* Shape */
--radius

/* Charts (1–5) */
--chart-1  --chart-2  --chart-3  --chart-4  --chart-5

/* Sidebar */
--sidebar  --sidebar-foreground  --sidebar-primary
--sidebar-primary-foreground  --sidebar-accent
--sidebar-accent-foreground  --sidebar-border  --sidebar-ring

/* Extended (v4.0.0) */
--surface             --surface-foreground
--code                --code-foreground
--code-highlight      --code-number
--selection           --selection-foreground

/* Typography */
--font-sans  --font-heading  --font-serif  --font-mono

Dark Mode

Dark mode is controlled by the presence of the dark class on the <html> element. All NeoUI components automatically switch to dark mode colors when this class is present.

The included theme.js script initialises the correct class on page load to prevent a flash of unstyled content. The DarkModeToggle component handles switching:

RZ
@* Add to your layout — toggles the .dark class on <html> *@
<DarkModeToggle />
Live toggle — try it

Using Tailwind CSS Alongside NeoUI

NeoUI works out of the box without Tailwind — components.css handles all component styling. If you want Tailwind utility classes in your own layouts and pages (e.g. bg-primary, text-muted-foreground, border-border), you can add a Tailwind v4 build pipeline to your Shared project. The key integration point is the tailwind.config.js — it maps all NeoUI CSS variables to named Tailwind colors, so your utility classes and the active NeoUI theme always stay in sync automatically.

1. Install Tailwind v4

Add a package.json in your Shared project (alongside the .csproj) and install the CLI:

Terminal
$ npm install

Or add the packages directly to your package.json:

{}package.json
{
  "scripts": {
    "build:css": "tailwindcss -i ./wwwroot/css/app-input.css -o ./wwwroot/css/app.css --minify"
  },
  "devDependencies": {
    "@tailwindcss/cli": "^4.2.0",
    "tailwindcss": "^4.2.0"
  }
}

2. Create the CSS input file

app-input.css is the entry point Tailwind reads. It pulls in Tailwind v4 itself, your NeoUI CSS variable theme, and any custom CSS you need. The compiled output is written to app.css.

CSSwwwroot/css/app-input.css
/* 1. Tell Tailwind v4 to use the config file for content scanning and theme extension */
@config "../../tailwind.config.js";

/* 2. Import Tailwind v4 — generates utilities, components, and base layers */
@import 'tailwindcss';

/* 3. IMPORTANT: prevents Tailwind from hardcoding oklch() values for CSS variable colors.
      Without this, dynamic theme switching breaks because color values get baked in at build time. */
@theme inline;

/* 4. Import your NeoUI CSS variable theme (defines --primary, --muted, etc.) */
@import '../styles/theme.css';

/* 5. Import base resets and portal styles */
@import '../styles/base.css';

/* 6. Add your own custom CSS layers below */
@layer base {
  :root {
    /* structural layout variables */
    --sidebar-width: 16rem;
  }
}

3. Configure tailwind.config.js

Place this file next to your .csproj. The content array tells Tailwind where to scan for class names. The theme.extend.colors section wires every NeoUI CSS variable to a named Tailwind color — this is what makes bg-primary or text-muted-foreground theme-aware.

JStailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  // Match NeoUI's dark mode strategy — .dark class on <html>
  darkMode: ['class'],

  // Scan all Razor, HTML, and CS files for Tailwind class names
  content: [
    './**/*.{razor,html,cs}',
    '../YourApp.Web/**/*.{razor,html,cs}',
    '../YourApp.Web.Client/**/*.{razor,html,cs}',
  ],

  theme: {
    extend: {
      fontFamily: {
        sans: "var(--font-sans, ui-sans-serif, system-ui, sans-serif)",
        mono: "var(--font-mono, ui-monospace, monospace)",
      },
      // Map every NeoUI CSS variable to a named Tailwind color.
      // bg-primary, text-muted-foreground, border-border etc. are now theme-aware.
      colors: {
        border:      "var(--border)",
        input:       "var(--input)",
        ring:        "var(--ring)",
        background:  "var(--background)",
        foreground:  "var(--foreground)",
        primary:     { DEFAULT: "var(--primary)",     foreground: "var(--primary-foreground)"     },
        secondary:   { DEFAULT: "var(--secondary)",   foreground: "var(--secondary-foreground)"   },
        muted:       { DEFAULT: "var(--muted)",       foreground: "var(--muted-foreground)"       },
        accent:      { DEFAULT: "var(--accent)",      foreground: "var(--accent-foreground)"      },
        destructive: { DEFAULT: "var(--destructive)", foreground: "var(--destructive-foreground)" },
        card:        { DEFAULT: "var(--card)",        foreground: "var(--card-foreground)"        },
        popover:     { DEFAULT: "var(--popover)",     foreground: "var(--popover-foreground)"     },
        sidebar:     { DEFAULT: "var(--sidebar)",     foreground: "var(--sidebar-foreground)"     },
      },
      borderRadius: {
        lg: "var(--radius)",
        md: "calc(var(--radius) - 2px)",
        sm: "calc(var(--radius) - 4px)",
      },
    },
  },
  plugins: [],
}

4. Integrate with the .NET build

Add MSBuild targets to your .csproj so dotnet build automatically runs npm install and compiles Tailwind before the Razor project builds.

XMLYourApp.Shared.csproj
<!-- Exclude npm build tooling from published output -->
<ItemGroup>
  <Content Remove="package.json" />
  <Content Remove="package-lock.json" />
  <Content Remove="tailwind.config.js" />
</ItemGroup>

<!-- Run npm install before Tailwind compiles -->
<Target Name="NpmInstall" BeforeTargets="BuildTailwindCss">
  <Exec Command="npm install" WorkingDirectory="$(ProjectDir)" />
</Target>

<!-- Compile Tailwind CSS automatically on every dotnet build -->
<Target Name="BuildTailwindCss" BeforeTargets="Build">
  <Exec Command="npm run build:css" WorkingDirectory="$(ProjectDir)" />
</Target>

5. Reference the output CSS in App.razor

Add the generated app.css after components.css. Use @Assets[...] so the URL is fingerprinted for cache invalidation. Replace YourApp.Shared with your Shared project's assembly name.

RZApp.razor
<head>
    <link rel="stylesheet" href="@Assets["_content/NeoUI.Blazor/components.css"]" />

    @* Tailwind-generated CSS — replace assembly name with your Shared project name *@
    <link rel="stylesheet" href="@Assets["_content/YourApp.Shared/css/app.css"]" />
</head>

Next Steps

Reconnecting...

Attempting to rejoin the server

Connection Lost

Retrying in seconds

Connection Failed

Failed to rejoin the server.
Please retry or reload the page.

Session Paused

The session has been paused by the server

Resume Failed

Failed to resume the session.
Please reload the page.