Skip to main content
The cn() utility function merges Tailwind CSS classes intelligently, handling class conflicts and conditional styling. It leverages the clsx-for-tailwind library to properly resolve conflicting Tailwind utility classes.

Function Signature

def cn(
    *classes: Var | str | tuple | list | None,
) -> Var:
    """Merge Tailwind CSS classes. Accepts strings, Vars, lists, or tuples.

    Args:
        *classes: Any number of class strings, Vars, tuples, or lists.

    Returns:
        Var: A Var representing the merged classes string.
    """

Parameters

*classes
Var | str | tuple | list | None
Any number of arguments that can be:
  • strings: Direct class names like "bg-blue-500 text-white"
  • Vars: Reflex Var objects containing class names
  • lists: Lists of class names
  • tuples: Tuples of class names
  • None: Ignored gracefully

Returns

A Var representing the merged classes string with conflicts resolved.

How It Works

The cn() function wraps the cn utility from the clsx-for-tailwind library, which:
  1. Combines multiple class name inputs
  2. Resolves conflicting Tailwind utility classes (e.g., later classes override earlier ones)
  3. Removes duplicates and normalizes the output
  4. Handles conditional classes and falsy values

Usage Examples

Basic Class Merging

Combine multiple class strings:
import reflex as rx
from reflex_ui.utils import cn

def button_example():
    classes = cn(
        "px-4 py-2",
        "bg-blue-500 hover:bg-blue-600",
        "text-white font-semibold rounded"
    )
    return rx.button("Click Me", class_name=classes)

Resolving Conflicting Classes

When classes conflict, later values override earlier ones:
def conflicting_classes():
    # The bg-red-500 will override bg-blue-500
    classes = cn(
        "bg-blue-500 text-white",
        "bg-red-500"  # This takes precedence
    )
    return rx.box(class_name=classes)  # Results in "text-white bg-red-500"

Conditional Styling

Apply classes conditionally using tuples or conditional expressions:
class State(rx.State):
    is_active: bool = False
    is_disabled: bool = False

def conditional_button():
    classes = cn(
        "px-4 py-2 rounded font-medium",
        rx.cond(
            State.is_active,
            "bg-green-500 text-white",
            "bg-gray-200 text-gray-700"
        ),
        rx.cond(
            State.is_disabled,
            "opacity-50 cursor-not-allowed",
            "hover:scale-105 transition-transform"
        )
    )
    return rx.button("Toggle", class_name=classes)

Merging with Component Props

Combine base classes with dynamic props:
def card(content: str, custom_class: str = ""):
    base_classes = "p-6 rounded-lg shadow-md bg-white"
    merged = cn(base_classes, custom_class)
    
    return rx.box(
        rx.text(content),
        class_name=merged
    )

# Usage
def page():
    return rx.vstack(
        card("Default card"),
        card("Blue card", "bg-blue-50 border border-blue-200"),
        card("Large card", "p-8 text-lg")
    )

Using with State Variables

Dynamically merge classes based on state:
class ThemeState(rx.State):
    variant: str = "primary"
    size: str = "md"

def styled_button():
    # Variant classes
    variant_classes = rx.match(
        ThemeState.variant,
        ("primary", "bg-blue-500 hover:bg-blue-600 text-white"),
        ("secondary", "bg-gray-500 hover:bg-gray-600 text-white"),
        ("outline", "border-2 border-blue-500 text-blue-500 hover:bg-blue-50"),
        "bg-blue-500 text-white"  # default
    )
    
    # Size classes
    size_classes = rx.match(
        ThemeState.size,
        ("sm", "px-3 py-1 text-sm"),
        ("md", "px-4 py-2 text-base"),
        ("lg", "px-6 py-3 text-lg"),
        "px-4 py-2"  # default
    )
    
    classes = cn(
        "rounded font-medium transition-colors",
        variant_classes,
        size_classes
    )
    
    return rx.button("Styled Button", class_name=classes)

Complex Component Styling

Create reusable components with variant support:
def alert(message: str, variant: str = "info", dismissible: bool = False):
    base_classes = "p-4 rounded border-l-4"
    
    variant_map = {
        "info": "bg-blue-50 border-blue-500 text-blue-900",
        "success": "bg-green-50 border-green-500 text-green-900",
        "warning": "bg-yellow-50 border-yellow-500 text-yellow-900",
        "error": "bg-red-50 border-red-500 text-red-900",
    }
    
    classes = cn(
        base_classes,
        variant_map.get(variant, variant_map["info"]),
        dismissible and "pr-10 relative"
    )
    
    return rx.box(
        rx.text(message),
        rx.cond(
            dismissible,
            rx.button(
                "×",
                class_name="absolute right-2 top-2 text-xl font-bold opacity-50 hover:opacity-100"
            )
        ),
        class_name=classes
    )

Integration with clsx-for-tailwind

The cn() function automatically imports and uses the cn utility from the clsx-for-tailwind npm package. This library provides:
  • Tailwind-aware merging: Understands Tailwind’s utility class structure
  • Conflict resolution: Properly handles conflicting utilities (e.g., bg-red-500 vs bg-blue-500)
  • Performance: Optimized for runtime class name generation
The import is handled automatically when you use the function:
from reflex_ui.utils import cn

# The clsx-for-tailwind package is automatically imported in the generated JavaScript

Best Practices

  1. Order matters for conflicts: Place more specific or override classes later in the arguments
  2. Use for variants: Ideal for component variants and conditional styling
  3. Combine with rx.cond(): Create dynamic, state-driven class names
  4. Keep base classes first: Put common, shared classes in the first argument
  5. Avoid inline conditionals: Use rx.cond() or rx.match() for cleaner conditional logic

Common Patterns

Button Variants

def button_with_variants(
    text: str,
    variant: str = "solid",
    color: str = "blue",
    size: str = "md"
):
    base = "font-medium rounded transition-all"
    
    sizes = {
        "sm": "px-3 py-1.5 text-sm",
        "md": "px-4 py-2 text-base",
        "lg": "px-6 py-3 text-lg"
    }
    
    variants = {
        "solid": f"bg-{color}-500 hover:bg-{color}-600 text-white",
        "outline": f"border-2 border-{color}-500 text-{color}-500 hover:bg-{color}-50",
        "ghost": f"text-{color}-500 hover:bg-{color}-50"
    }
    
    return rx.button(
        text,
        class_name=cn(base, sizes[size], variants[variant])
    )

Responsive Classes

def responsive_grid():
    classes = cn(
        "grid gap-4",
        "grid-cols-1",          # Mobile
        "sm:grid-cols-2",       # Tablet
        "lg:grid-cols-3",       # Desktop
        "xl:grid-cols-4"        # Large desktop
    )
    return rx.box(class_name=classes)

See Also

Build docs developers (and LLMs) love