Mastering Themes & Styling in RizzyUI
Hardcoded styles are technical debt. If you’ve ever had to grep-replace hex codes across fifty files because the marketing team changed the primary brand color, you know the pain.
In this guide, we’ll configure a robust, type-safe styling system that handles dark mode automatically, supports runtime theme switching, and lets you modify component structures without fighting global CSS.
Prerequisites & Installation
RizzyUI is designed for modern, server-centric .NET development.
Requirements:
- .NET 10 (Blazor Web App).
- Hosting Model: Static Server Rendering (SSR). RizzyUI does not require WebAssembly.
- Tailwind CSS 4.0+ (CLI).
- Node.js (for fetching the Tailwind configuration/styles).
Step 1: Install Packages
You need the .NET components, the Tailwind styles, and the Tailwind CLI.
NuGet (The Components):
dotnet add package RizzyUINPM (The Styles & Build Tool):
npm install @jalexsocial/rizzyui
npm install -D tailwindcss @tailwindcss/cliThe Build Pipeline (Tailwind 4)
RizzyUI ships a CSS file (rizzyui-plugin.css) that acts as a Tailwind 4 configuration source. It contains the @theme definitions, reset layers, and animation utilities.
Step 1: Create your Input CSS
Create a file at Styles/app.css (or your preferred location).
@import "tailwindcss";
/*
Import RizzyUI Core.
This pulls in the base theme variables, form resets, and animations.
If your toolchain supports node resolution (e.g. Vite), use the package name.
Otherwise, use the relative path to node_modules.
*/
@import "@jalexsocial/rizzyui/dist/css/rizzyui-plugin.css";
/*
Tell Tailwind to scan your Razor files and C# theme definitions.
CRITICAL: This path is relative to THIS css file. Adjust as needed.
We exclude bin/obj to prevent scanning generated files.
*/
@source "../**/*.{razor,html,cs}";
@source "!../**/bin/**";
@source "!../**/obj/**";Step 2: Configure NPM Scripts
Add these scripts to your package.json to make running the build easy for your team.
{
"scripts": {
"tailwind:watch": "npx @tailwindcss/cli -i ./Styles/app.css -o ./wwwroot/app.css --watch",
"tailwind:build": "npx @tailwindcss/cli -i ./Styles/app.css -o ./wwwroot/app.css --minify"
}
}Step 3: Validation
Open two terminals.
- Run
dotnet watch. - Run
npm run tailwind:watch.
If you add a class like bg-red-500 to a Razor component, you should see the Tailwind CLI report a rebuild immediately, and the style should appear in the browser.
Application Wiring
We need to register the services and inject the theme variables into the document head.
Step 1: Global Registration
In Program.cs, add the service and set your baseline theme.
builder.Services.AddRizzyUI(config =>
{
// Sets the baseline styling for all components
config.DefaultTheme = RzTheme.ArcticTheme;
});Step 2: Inject the Provider (App.razor)
In App.razor, add the head components. This file owns the HTML document structure in .NET 10 Blazor Web Apps.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<!-- 1. Your compiled Tailwind CSS -->
<link rel="stylesheet" href="app.css" />
<!-- 2. RizzyUI Head Outlet
- Manages Page Titles / Meta
- Injects the Alpine.js runtime (required for interactivity)
-->
<RzHeadOutlet />
<!-- 3. The Theme Provider
- Must be in <head> to prevent Flash of Unstyled Content (FOUC)
- Calculates and injects CSS variables for the current theme
-->
<RzThemeProvider Theme="@CurrentTheme" />
</head>
<body>
<Routes />
<!--
Note: blazor.web.js is optional for RizzyUI itself.
RizzyUI uses Alpine.js + HTMX for interactivity.
Only include this if your app uses other InteractiveServer components.
-->
<script src="_framework/blazor.web.js"></script>
</body>
</html>
@code {
// We bind this to the provider to allow runtime switching later
private RzTheme CurrentTheme { get; set; } = RzTheme.ArcticTheme;
}Note on Alpine.js
RizzyUI components use Alpine.js for client-side interactivity (dropdowns, toggles, etc.).
- How it loads: The
<RzHeadOutlet />automatically injects a script tag pointing to the bundled Alpine runtime in_content/RizzyUI/js/rizzyui.js. - CSP: If you use a Content Security Policy, allow scripts from
_content/RizzyUI.
Understanding Design Tokens (Shadcn/UI)
RizzyUI implements the Shadcn/UI token system. Instead of picking specific colors like Blue or Red, you pick semantic roles.
- Primary: The main brand color (buttons, active links).
- Destructive: Critical actions (delete buttons, errors).
- Background: The page background.
- Foreground: Text and icons.
- Muted: Subtle text or secondary backgrounds.
Why this matters
When you use semantic tokens, Dark Mode comes for free.
<!-- Bad: Needs manual dark mode handling -->
<div class="bg-white text-black dark:bg-slate-900 dark:text-white">...</div>
<!-- Good: RizzyUI handles the swap automatically -->
<div class="bg-background text-foreground">...</div>Under the hood, RzThemeProvider injects CSS variables via a <style> tag. In Light mode, --background maps to white. In Dark mode, it swaps the definition of --background to slate-950. Your HTML markup never changes.
The Color Palette System
While semantic tokens handle the UI roles, RizzyUI also exposes the full Tailwind color palette in C# for scenarios where you need specific shades (e.g., defining a custom theme).
The library defines a set of color scales exposed via the static RizzyUI.Colors class. For example, the color scale for Rose is accessed as Colors.Rose, and its various shades are provided as properties like L50, L100, …, L950.
Color Scales Overview
The following table lists all the color scales defined in RizzyUI Colors along with their corresponding Tailwind color names:
| ColorScale | Tailwind Color Name |
|---|---|
| Amber | amber |
| Blue | blue |
| Cyan | cyan |
| Emerald | emerald |
| Fuchsia | fuchsia |
| Gray | gray |
| Green | green |
| Indigo | indigo |
| Lime | lime |
| Neutral | neutral |
| Orange | orange |
| Pink | pink |
| Purple | purple |
| Red | red |
| Rose | rose |
| Sky | sky |
| Slate | slate |
| Stone | stone |
| Teal | teal |
| Violet | violet |
| Yellow | yellow |
| Zinc | zinc |
How Shades Map to Tailwind Classes
Each color scale class (e.g. Colors.Rose) exposes properties representing different intensity levels. These properties are named following the pattern L
Colors.Rose.L500
This property returns the color corresponding torose-500in Tailwind.Colors.Blue.L200
This is equivalent to the Tailwind classblue-200.
Details
Numeric Scale:
The numeric value (L50, L100, L200, etc.) directly maps to the intensity in Tailwind. Lower numbers (e.g., L50, L100) are lighter shades, while higher numbers (e.g., L900, L950) are darker shades.Usage in Components:
When using theme tokens in your component styles (via Tailwind classes likebg-primaryortext-foreground), the underlying theme may reference these color scales. For instance, a theme might set its primary color asColors.Blue.L700so that any component usingbg-primarywill get the same blue tone as defined by Tailwind’sblue-700.Consistency:
This direct mapping ensures that when you use a color scale in code—for example, in a custom theme or a component’s variant—the numeric suffix always corresponds to the same Tailwind color. It simplifies maintenance and helps guarantee visual consistency across your application.
Example
If you want to apply the “Rose” color at intensity 500 as a background, you could use the following Tailwind class:
<div class="bg-rose-500 text-foreground">
This div uses the rose color at intensity 500.
</div>Under the hood, Colors.Rose.L500 in the theme provider will supply the same color value as Tailwind’s rose-500.
Warning: Avoid using raw color classes (like
bg-rose-500) directly in your Razor markup for main layout elements. If you do, those elements won't automatically adapt when the user toggles Dark Mode. Stick to semantic tokens likebg-primary,bg-background(Surface),bg-card, etc.
Loading External Themes (JSON)
Manually configuring every color property in C# can be tedious. RizzyUI supports the standard Shadcn/UI JSON Registry schema. This means you can use visual tools like TweakCN to design your theme, export the JSON, and load it directly into your .NET application.
Step 1: Get the JSON
Export your theme from TweakCN or grab a Shadcn theme file. Save it as my-theme.json in your project (e.g., in wwwroot/themes or as an Embedded Resource).
Step 2: Load at Startup
In Program.cs, use the ThemeLoader to hydrate the theme object. In .NET 10, we can use top-level await.
// Program.cs
// Load the theme file asynchronously before building the app
var customTheme = await ThemeLoader.LoadFromFileAsync("wwwroot/themes/my-theme.json");
builder.Services.AddRizzyUI(config => {
// Apply the loaded theme.
// Fallback to Arctic if the file failed to load.
config.DefaultTheme = customTheme ?? RzTheme.ArcticTheme;
});This allows you to change your entire application's color palette without recompiling, simply by swapping the JSON file.
Component Customization (TailwindVariants)
Sometimes you don't just want to change colors; you want to change the structure or default behavior of a component across the entire app.
RizzyUI uses TailwindVariants.NET to define component styles. This allows you to define "slots" (different parts of a component) and "variants" (different states).
The Problem
You want all buttons in your application to be "pill" shaped (fully rounded) and uppercase, but RzButton defaults to slightly rounded corners (rounded-md) and normal casing.
The Solution: Override the Descriptor
You can override the TvDescriptor for any component within your theme configuration.
// 1. Create a custom theme class
public class MyCustomTheme : ArcticTheme
{
public MyCustomTheme()
{
// 2. Get the default button definition to copy existing logic
var defaultBtn = RizzyUI.RzButton.DefaultDescriptor;
// 3. Create a new descriptor
// We modify the 'Base' slot to add our new classes.
// TailwindMerge (built-in) will automatically resolve conflicts
// (e.g., 'rounded-full' will overwrite 'rounded-md').
this.RzButton = new TvDescriptor<RzComponent<RzButton.Slots>, RzButton.Slots>(
@base: defaultBtn.Base + " rounded-full uppercase tracking-widest",
variants: defaultBtn.Variants,
slots: defaultBtn.Slots
);
}
}To activate this:
builder.Services.AddRizzyUI(config => {
config.DefaultTheme = new MyCustomTheme();
});Runtime Theme Switching & Dark Mode
RizzyUI handles dark mode toggling automatically, but switching entire theme definitions (e.g. Blue vs Red) requires explicit state management in SSR.
Dark Mode (Automatic)
Use the built-in component:
<RzDarkModeToggle />This component uses Alpine.js to toggle the .dark class on the <html> element and persists the preference to localStorage. RzThemeProvider detects this class and updates the CSS variables immediately.
Switching Theme Definitions (Manual)
To switch from ArcticTheme to VercelTheme at runtime, you update the Theme parameter on the single <RzThemeProvider> we added to App.razor.
Important: In SSR, this state lives on the server during the request. To make it persist across navigation, you must store the user's choice (e.g., in a cookie or database) and read it on page load.
<!-- In a Layout or Page -->
<form method="post" action="/settings/theme" data-enhance>
<input type="hidden" name="theme" value="vercel" />
<button type="submit">Switch to Vercel</button>
</form>(The implementation of the controller/endpoint to set the cookie is standard ASP.NET Core logic).
Troubleshooting
I see unstyled components.
- Did you run the Tailwind build script? (
npm run tailwind:watch) - Is the output CSS file linked in
App.razor? - Check your
@sourceglob inapp.css. It must find your.razorand.csfiles relative to whereapp.csslives.
- Did you run the Tailwind build script? (
Interactive UI (dropdowns, toggles) isn't responding.
- Confirm
<RzHeadOutlet />is in<head>. - Check the browser console network tab—is
rizzyui.jsloading? - Check if a Content Security Policy (CSP) is blocking the script.
- Confirm
My descriptor changes aren't showing up.
- Tailwind Scanning: If you add a class string like
"bg-fuchsia-500"inside a C# file, Tailwind's scanner must see that file. Ensure your@sourcedirective includes**/*.cs. - Computed Classes: Tailwind cannot see dynamically computed strings like
$"bg-{color}-500". You must use full class names in your C# code or add them to a safelist.
- Tailwind Scanning: If you add a class string like
Theme switches but resets on refresh.
- Dark mode persists automatically via
localStorage. - Theme Definition (Arctic vs Custom) selection is server-side state. You must persist this selection (Cookie/DB) and reload it in
App.razororProgram.cs.
- Dark mode persists automatically via
Key Takeaways
- Runtime Power: Themes are C# objects injected as CSS variables by
RzThemeProvider. - Build Pipeline: Use Tailwind CLI to compile your CSS; importing the NPM package is just the configuration step.
- SSR First: No WASM required. Interactivity is handled by Alpine.js.
- Tokens: Use semantic classes (
bg-primary) instead of raw colors (bg-blue-500) to get Dark Mode for free.
Thanks for reading—now go build something beautiful!