Skip to main content
Reflex UI components are built with Tailwind CSS, making them highly customizable while maintaining a consistent design system. Every component accepts a class_name prop that lets you add custom styles.

The class_name Prop

All Reflex UI components accept a class_name prop for custom styling:
import reflex_ui as ui

# Add custom Tailwind classes
ui.button(
    "Click me",
    class_name="font-bold uppercase tracking-wide"
)

# Responsive design
ui.button(
    "Responsive",
    class_name="w-full md:w-auto md:px-8"
)

# Custom colors and spacing
ui.card(
    title="Custom Card",
    class_name="bg-gradient-to-r from-primary-9 to-info-9 p-8"
)

How Style Merging Works

Reflex UI uses the cn() utility (powered by clsx and tailwind-merge) to intelligently merge default styles with your custom classes:
reflex_ui/components/component.py
@classmethod
def set_class_name(
    cls, default_class_name: str | Var[str], props: dict[str, Any]
) -> None:
    """Set the class name in props, merging with the default if necessary."""
    props_class_name = props.get("class_name", "")

    if props.pop("unstyled", False):
        props["class_name"] = props_class_name
        return

    props["class_name"] = cn(default_class_name, props_class_name)
This means:
  • Default styles are preserved: Your component keeps its base styling
  • Conflicts are resolved: Later classes override earlier ones (e.g., bg-red-500 overrides bg-blue-500)
  • Non-conflicting classes are merged: Padding, margin, and other properties are combined
If you want to completely remove default styles, use the unstyled=True prop and build from scratch.

The cn() Utility

The cn() function is Reflex UI’s class name merging utility:
reflex_ui/utils/twmerge.py
def cn(
    *classes: Var | str | tuple | list | None,
) -> Var:
    """Merge Tailwind CSS classes. Accepts strings, Vars, lists, or tuples."""
    return (
        Var(
            "cn",
            _var_data=VarData(imports={"clsx-for-tailwind": ImportVar(tag="cn")}),
        )
        .to(FunctionVar)
        .call(*classes)
        .to(str)
    )

Using cn() in Your Code

You can import and use cn() in your own components:
from reflex_ui.utils.twmerge import cn
import reflex as rx
import reflex_ui as ui

def custom_button(**props):
    base_classes = "px-4 py-2 rounded-lg font-medium"
    variant_classes = "bg-gradient-to-r from-purple-500 to-pink-500"
    user_classes = props.pop("class_name", "")
    
    return rx.el.button(
        "Custom Button",
        class_name=cn(base_classes, variant_classes, user_classes),
        **props
    )

Conditional Classes

Combine cn() with Reflex’s cond() for dynamic styling:
from reflex.components.core.cond import cond
from reflex_ui.utils.twmerge import cn

class State(rx.State):
    is_active: bool = False

def conditional_button():
    return ui.button(
        "Toggle",
        on_click=State.toggle_active,
        class_name=cond(
            State.is_active,
            "bg-success-9 text-white",
            "bg-secondary-4 text-secondary-11"
        )
    )

Common Styling Patterns

Override Variant Styles

Components have default variants, but you can override specific properties:
# Change the background color of a primary button
ui.button(
    "Custom Primary",
    variant="primary",
    class_name="bg-gradient-to-r from-blue-500 to-cyan-500"
)

# Adjust the border radius
ui.card(
    title="Rounded Card",
    class_name="rounded-3xl"
)

Responsive Design

Use Tailwind’s responsive prefixes:
ui.button(
    "Responsive Button",
    class_name="w-full sm:w-auto px-4 sm:px-8 text-sm sm:text-base"
)

ui.card(
    title="Responsive Card",
    class_name="p-4 md:p-8 lg:p-12"
)

Dark Mode Styling

Use the dark: prefix for dark mode-specific styles:
ui.card(
    title="Dark Mode Card",
    class_name="bg-white dark:bg-secondary-12 text-black dark:text-white"
)
Reflex UI components automatically adapt to dark mode using CSS variables. Manual dark mode classes are only needed for custom styling.

State-Based Styling

Use state variants like hover:, focus:, disabled:, and data-*::
ui.button(
    "Interactive",
    class_name="hover:scale-105 active:scale-95 transition-transform"
)

ui.checkbox(
    label="Custom Checkbox",
    class_name="data-[checked]:border-success-9 data-[checked]:bg-success-9"
)

Working with Component Variants

Many components define variant systems with predefined styles:

Button Variants

reflex_ui/components/base/button.py
BUTTON_VARIANTS = {
    "variant": {
        "primary": "bg-primary-9 text-primary-contrast hover:bg-primary-10",
        "destructive": "bg-destructive-9 hover:bg-destructive-10 text-primary-contrast",
        "outline": "border border-secondary-a4 bg-secondary-1 hover:bg-secondary-3",
        "secondary": "bg-secondary-4 text-secondary-12 hover:bg-secondary-5",
        "ghost": "hover:bg-secondary-3 text-secondary-11",
        "link": "text-secondary-12 underline-offset-4 hover:underline",
        "dark": "bg-secondary-12 text-secondary-1 hover:bg-secondary-12/80",
    },
    "size": {
        "xs": "px-1.5 h-7 rounded-ui-xs gap-1.5",
        "sm": "px-2 h-8 rounded-ui-sm gap-2",
        "md": "px-2.5 h-9 rounded-ui-md gap-2",
        "lg": "px-3 h-10 rounded-ui-lg gap-2.5",
        "xl": "px-3.5 h-12 rounded-ui-xl gap-3",
    },
}
Use these variants and customize as needed:
# Use a variant
ui.button("Primary", variant="primary", size="lg")

# Customize a variant
ui.button(
    "Custom Primary",
    variant="primary",
    size="lg",
    class_name="rounded-full shadow-lg"
)

The unstyled Prop

When you need complete control, use unstyled=True to remove all default styles:
# Completely unstyled button
ui.button(
    "Custom",
    unstyled=True,
    class_name="bg-gradient-to-r from-green-400 to-blue-500 px-6 py-3 rounded-full text-white font-bold shadow-xl hover:shadow-2xl transition-all"
)

# Unstyled card
ui.card(
    title="Custom Card",
    unstyled=True,
    class_name="bg-glass backdrop-blur-lg border border-white/20 p-8 rounded-2xl"
)
When using unstyled=True, you’re responsible for all styling, including:
  • Layout (flexbox, grid, etc.)
  • Spacing (padding, margin)
  • Colors (background, text, borders)
  • Interactive states (hover, focus, active)
  • Accessibility (focus rings, contrast)

Accessing Component Class Names

Composite components expose their class names for granular customization:
from reflex_ui.components.base.card import card

# Access default class names
print(card.class_names.ROOT)      # "rounded-ui-xl border ..."
print(card.class_names.HEADER)    # "flex flex-col px-6 pt-6 pb-4"
print(card.class_names.TITLE)     # "text-2xl font-semibold ..."

# Build custom composite components
def custom_card():
    return card.root(
        card.header(
            card.title("Custom Title", class_name="text-4xl text-primary-9"),
            class_name="pb-8"
        ),
        card.content(
            "Content here",
            class_name="text-lg"
        ),
        class_name="shadow-2xl border-2 border-primary-9"
    )

Best Practices

Prefer semantic color tokens over raw Tailwind colors:
# Good - uses theme colors
ui.button("Submit", class_name="bg-primary-9 text-primary-contrast")

# Avoid - hardcoded colors won't adapt to theme changes
ui.button("Submit", class_name="bg-violet-600 text-white")
Work with variants rather than replacing them entirely:
# Good - extends the variant
ui.button("Submit", variant="primary", class_name="shadow-xl")

# Avoid - replaces too much
ui.button("Submit", unstyled=True, class_name="...everything...")
Embrace Tailwind’s utility classes rather than custom CSS:
# Good - uses utilities
ui.card(class_name="hover:shadow-xl transition-shadow duration-300")

# Avoid - custom CSS breaks consistency
ui.card(style={"boxShadow": "0 20px 25px -5px rgba(0,0,0,0.1)"})

Next Steps

Theming

Learn how to customize the color scheme and implement dark mode.

Component Reference

Explore the full component library with examples.

Build docs developers (and LLMs) love