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:
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
@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.75 rem ; /* 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
tomato, red, ruby, crimson - Reds
pink, plum, purple, violet - Purples
iris, indigo, blue, cyan - Blues
teal, jade, green, grass - Greens
lime, mint, sky - Light colors
yellow, amber, orange, brown - Warm colors
bronze, gold - Metallics
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.5 rem ; /* Default: 8px */
/* Computed radius values for different component sizes */
--radius-ui-xxs : calc ( var ( --radius ) - 0.25 rem ); /* 4px */
--radius-ui-xs : calc ( var ( --radius ) - 0.125 rem ); /* 6px */
--radius-ui-sm : var ( --radius ); /* 8px */
--radius-ui-md : calc ( var ( --radius ) + 0.125 rem ); /* 10px */
--radius-ui-lg : calc ( var ( --radius ) + 0.25 rem ); /* 12px */
--radius-ui-xl : calc ( var ( --radius ) + 0.375 rem ); /* 14px */
--radius-ui-2xl : calc ( var ( --radius ) + 0.5 rem ); /* 16px */
}
Customize the base radius to change all components at once:
:root {
--radius : 0 rem ; /* Sharp corners */
--radius : 0.25 rem ; /* Subtle rounding */
--radius : 1 rem ; /* Very rounded */
--radius : 9999 px ; /* 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:
Default (Violet)
Blue & Modern
Green & Natural
Sharp & Minimal
:root {
--primary-9 : var ( --violet-9 );
--secondary-1 : var ( --slate-1 );
--radius : 0.5 rem ;
}
:root {
--primary-9 : var ( --blue-9 );
--secondary-1 : var ( --gray-1 );
--radius : 0.75 rem ;
}
:root {
--primary-9 : var ( --jade-9 );
--secondary-1 : var ( --sage-1 );
--radius : 0.5 rem ;
}
:root {
--primary-9 : var ( --slate-12 );
--secondary-1 : var ( --slate-1 );
--radius : 0.125 rem ;
}
Best Practices
Use Semantic Color Tokens
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" )
Test in Both Light and Dark Mode
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.