Theming

NeoUI is 100% compatible with shadcn/ui themes. Use any theme from the shadcn/ui gallery, generate one with tweakcn, or write your own CSS variables.

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 five base palettes and seventeen primary accent colors, giving you 85 ready-to-use combinations out of the box.

Pre-Built Theme System

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

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

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

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

<!-- 4. Theme initialization (prevents FOUC) -->
<script src="@Assets["_content/NeoUI.Blazor/js/theme.js"]"></script>

Base colors (5)

  • zinc · slate · gray · neutral · stone

Primary accent colors (17)

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

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 *@
<ThemeSwitcher />
<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 before components.css:

RZApp.razor
<!-- Your custom theme variables — load BEFORE components.css -->
<link rel="stylesheet" href="styles/theme.css" />
<link rel="stylesheet" href="@Assets["_content/NeoUI.Blazor/components.css"]" />

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

/* Typography */
--font-sans  --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. Import your NeoUI CSS variable theme (defines --primary, --muted, etc.) */
@import '../styles/theme.css';

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

/* 5. 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.