Skip to main content
Reflex UI uses CSS custom properties (variables) for theming, providing a flexible system that supports light mode, dark mode, and custom themes without requiring component changes.

Color System

Reflex UI’s color system is based on Radix Colors, which provides perceptually uniform color scales optimized for UI design.

Color Scale Structure

Each color has a 12-step scale, where each step serves a specific purpose:
  • Steps 1-2: App backgrounds
  • Steps 3-5: Component backgrounds (hover, active states)
  • Steps 6-8: Borders and separators
  • Steps 9-10: Solid backgrounds (hover states)
  • Steps 11-12: Text (high contrast)

Semantic Colors

Reflex UI defines semantic color tokens that map to Radix colors:
demo/assets/css/globals.css
:root {
  /* Primary - Main brand color */
  --primary-1: var(--violet-1);
  --primary-2: var(--violet-2);
  --primary-9: var(--violet-9);    /* Primary actions */
  --primary-10: var(--violet-10);  /* Primary hover */
  --primary-11: var(--violet-11);  /* Primary text */
  --primary-12: var(--violet-12);

  /* Secondary - Neutral gray scale */
  --secondary-1: var(--slate-1);   /* Page background */
  --secondary-3: var(--slate-3);   /* Subtle backgrounds */
  --secondary-4: var(--slate-4);   /* UI elements */
  --secondary-9: var(--slate-9);   /* Borders */
  --secondary-11: var(--slate-11); /* Secondary text */
  --secondary-12: var(--slate-12); /* Primary text */

  /* Semantic state colors */
  --destructive-9: var(--red-9);   /* Error, delete actions */
  --success-9: var(--jade-9);      /* Success states */
  --warning-9: var(--amber-9);     /* Warnings */
  --info-9: var(--blue-9);         /* Information */

  /* Contrast text */
  --primary-contrast: #ffffff;     /* Text on primary background */
}

Using Theme Colors

In Tailwind CSS, theme colors are available as utility classes:
import reflex_ui as ui

# Background colors
ui.card(class_name="bg-primary-9")      # Primary solid
ui.card(class_name="bg-secondary-1")    # Page background
ui.card(class_name="bg-destructive-9")  # Error state

# Text colors
rx.text("Primary text", class_name="text-secondary-12")
rx.text("Secondary text", class_name="text-secondary-11")
rx.text("On primary", class_name="text-primary-contrast")

# Border colors
ui.card(class_name="border border-secondary-4")
ui.card(class_name="border-2 border-primary-9")

Dark Mode

Reflex UI supports dark mode out of the box using Tailwind CSS v4’s light-dark() function and the @custom-variant directive.

How Dark Mode Works

Dark mode is controlled by adding a dark class to the document root:
demo/assets/css/globals.css
@custom-variant dark (&:where(.dark, .dark *));
Radix Colors automatically provide dark mode variants for all color scales. When the dark class is present, components automatically use dark mode colors.

Theme Switcher Component

Reflex UI includes a theme_switcher component for toggling between light, dark, and system modes:
demo/demo/demo.py
import reflex_ui as ui

def index():
    return rx.el.div(
        # Your content here
        ui.theme_switcher(class_name="absolute top-4 right-4"),
        class_name="flex h-screen bg-secondary-1"
    )

Theme Switcher Implementation

reflex_ui/components/base/theme_switcher.py
def theme_switcher(class_name: str = "") -> Component:
    """Theme switcher component."""
    return Div.create(
        theme_switcher_item("system", "ComputerIcon"),
        theme_switcher_item("light", "Sun01Icon"),
        theme_switcher_item("dark", "Moon02Icon"),
        class_name=cn(
            "flex flex-row items-center bg-secondary-3 p-1 rounded-ui-md w-fit",
            class_name,
        ),
    )
The theme switcher:
  • Provides three modes: system (follows OS preference), light, and dark
  • Uses Reflex’s color_mode state and set_color_mode event
  • Adapts its own appearance to the current theme
  • Can be positioned anywhere using class_name

Manual Dark Mode Styling

For custom styling that differs between light and dark mode, use the dark: prefix:
import reflex as rx
import reflex_ui as ui

# Component that changes in dark mode
ui.card(
    title="Themed Card",
    class_name="""
        bg-white dark:bg-secondary-12
        text-gray-900 dark:text-gray-100
        border-gray-200 dark:border-gray-800
    """
)

# Image that inverts in dark mode
rx.image(
    src="/logo.png",
    class_name="dark:invert"
)
In most cases, you don’t need manual dark mode classes. Reflex UI components automatically adapt using CSS variables.

Creating a Custom Theme

To create a custom theme, override the CSS variables in your global stylesheet:

Step 1: Create a globals.css File

Create a CSS file (e.g., assets/css/globals.css) and import it in your app:
import reflex as rx

app = rx.App(
    stylesheets=["css/globals.css"],
)

Step 2: Override Color Variables

assets/css/globals.css
@import "tailwindcss";

@custom-variant dark (&:where(.dark, .dark *));

:root {
  /* Custom primary color - use your brand color */
  --primary-1: var(--blue-1);
  --primary-2: var(--blue-2);
  --primary-3: var(--blue-3);
  --primary-4: var(--blue-4);
  --primary-5: var(--blue-5);
  --primary-6: var(--blue-6);
  --primary-7: var(--blue-7);
  --primary-8: var(--blue-8);
  --primary-9: var(--blue-9);
  --primary-10: var(--blue-10);
  --primary-11: var(--blue-11);
  --primary-12: var(--blue-12);

  /* Custom neutral color - try different gray variants */
  --secondary-1: var(--mauve-1);
  --secondary-2: var(--mauve-2);
  --secondary-3: var(--mauve-3);
  --secondary-4: var(--mauve-4);
  --secondary-5: var(--mauve-5);
  --secondary-6: var(--mauve-6);
  --secondary-7: var(--mauve-7);
  --secondary-8: var(--mauve-8);
  --secondary-9: var(--mauve-9);
  --secondary-10: var(--mauve-10);
  --secondary-11: var(--mauve-11);
  --secondary-12: var(--mauve-12);

  /* Custom border radius */
  --radius: 0.75rem;  /* More rounded */

  /* Custom fonts */
  --font-sans: "Inter", sans-serif;
  --font-mono: "Fira Code", monospace;
}

@theme {
  /* Map to Tailwind theme */
  --color-primary-9: var(--primary-9);
  --color-secondary-1: var(--secondary-1);
  /* ... map other colors ... */
}

Available Color Palettes

Radix Colors provides many palettes to choose from:
  • gray - Pure gray
  • mauve - Purple-tinted gray
  • slate - Blue-tinted gray
  • sage - Green-tinted gray
  • olive - Yellow-tinted gray
  • sand - Brown-tinted gray

Step 3: Use Your Theme

Once configured, your custom theme is automatically applied to all components:
import reflex as rx
import reflex_ui as ui

def app():
    return rx.el.div(
        ui.button("Primary Button"),  # Uses --primary-9
        ui.button("Secondary", variant="secondary"),  # Uses --secondary-4
        ui.card(
            title="Themed Card",
            description="Automatically uses your custom colors",
        ),
        ui.theme_switcher(),
        class_name="min-h-screen bg-secondary-1 p-8"
    )

Custom Radius and Spacing

Reflex UI provides CSS variables for consistent border radius across components:
:root {
  /* Base radius */
  --radius: 0.5rem;  /* Default: 8px */

  /* Computed radius values for different component sizes */
  --radius-ui-xxs: calc(var(--radius) - 0.25rem);  /* 4px */
  --radius-ui-xs: calc(var(--radius) - 0.125rem);  /* 6px */
  --radius-ui-sm: var(--radius);                    /* 8px */
  --radius-ui-md: calc(var(--radius) + 0.125rem);  /* 10px */
  --radius-ui-lg: calc(var(--radius) + 0.25rem);   /* 12px */
  --radius-ui-xl: calc(var(--radius) + 0.375rem);  /* 14px */
  --radius-ui-2xl: calc(var(--radius) + 0.5rem);   /* 16px */
}
Customize the base radius to change all components at once:
:root {
  --radius: 0rem;      /* Sharp corners */
  --radius: 0.25rem;   /* Subtle rounding */
  --radius: 1rem;      /* Very rounded */
  --radius: 9999px;    /* Pill-shaped */
}

Loading Custom Fonts

Add custom fonts using Reflex’s head components:
import reflex as rx

app = rx.App(
    stylesheets=["css/globals.css"],
    head_components=[
        # Google Fonts
        rx.el.link(
            rel="preconnect",
            href="https://fonts.googleapis.com",
        ),
        rx.el.link(
            rel="preconnect",
            href="https://fonts.gstatic.com",
            crossorigin="",
        ),
        rx.el.link(
            href="https://fonts.googleapis.com/css2?family=Inter:[email protected]&display=swap",
            rel="stylesheet",
        ),
    ],
)
Then reference the font in your CSS:
:root {
  --font-sans: "Inter", sans-serif;
}

@theme {
  --font-sans: var(--font-sans);
}

Theme Presets

Here are some popular theme configurations:
:root {
  --primary-9: var(--violet-9);
  --secondary-1: var(--slate-1);
  --radius: 0.5rem;
}

Best Practices

Always use semantic tokens (primary-9, secondary-11) rather than Radix colors directly (violet-9, slate-11) in your components. This ensures your components adapt when themes change.
# Good
ui.button("Submit", class_name="bg-primary-9")

# Avoid
ui.button("Submit", class_name="bg-violet-600")
Always test your custom styling in both light and dark mode to ensure proper contrast and readability.
# Include the theme switcher during development
def dev_layout():
    return rx.el.div(
        ui.theme_switcher(class_name="fixed top-4 right-4 z-50"),
        # Your content
    )
When creating custom themes, maintain the full 1-12 color scale for each semantic color to ensure all UI states work correctly.

Next Steps

Styling Components

Learn advanced styling techniques with the class_name prop.

Component Library

Explore all available components and their variants.

Build docs developers (and LLMs) love