How to Change Styles Based on a Condition in React

Beginner8 min interview
Skills tested:
Applying conditional class names using a ternary inside classNameUsing the cn() or clsx utility to merge multiple conditional class names cleanlyApplying inline styles for truly dynamic runtime valuesKnowing the difference between className-based and inline-style-based conditional stylingHandling multiple simultaneous conditions on a single element

Advertisement

🧩 Scenario

Conditional styling is needed for every interactive UI: active nav links, error state inputs, disabled buttons, selected list items, and loading skeletons. A React engineer should know at least three approaches and be able to explain the tradeoffs between them without reaching for inline styles by default.

Architecture Walkthrough

className with Ternary

The most common pattern is a ternary inside the className prop. The element always applies its base styles; the ternary adds or swaps a conditional class based on a state or prop value. This keeps all style definitions in CSS (or Tailwind), where they can be inspected, reused, and overridden.

This pattern works for a single boolean condition. When there are two or more independent conditions, string concatenation becomes unwieldy. That is when the cn() utility becomes the right tool.

The cn() Utility

cn() is a wrapper around clsx and tailwind-merge that handles conditional class merging cleanly. It accepts any mix of strings, booleans, and objects ({ 'class-name': condition }) and produces a single className string with duplicates and false values removed. For Tailwind specifically, tailwind-merge resolves conflicting utility classes (e.g., both text-sm and text-lg on the same element) by keeping the last one.

This project uses cn() from src/lib/utils.ts for all conditional class merging.

Inline Styles for Dynamic Values

Inline styles are appropriate for style values that cannot be expressed as static CSS classes because they are computed at runtime from data: a progress bar width from a number, a color selected by the user from a color picker, a transform value calculated from mouse position. For anything that can be a Tailwind class or a CSS class, use className.


Key Code Explained

// Pattern 1: Single condition — ternary in className
function NavLink({ href, label }: { href: string; label: string }) {
  const pathname = usePathname();
  const isActive = pathname === href;

  return (
    <a
      href={href}
      // Ternary: active gets a different set of classes, inactive gets the default
      className={isActive
        ? 'font-semibold text-blue-600 underline'
        : 'text-gray-600 hover:text-blue-600'
      }
    >
      {label}
    </a>
  );
}


// Pattern 2: Multiple conditions — cn() utility
import { cn } from '@/lib/utils';

interface InputProps {
  value: string;
  onChange: (value: string) => void;
  error?: string;
  success?: boolean;
  disabled?: boolean;
}

function ValidatedInput({ value, onChange, error, success, disabled }: InputProps) {
  return (
    <input
      value={value}
      onChange={(e) => onChange(e.target.value)}
      disabled={disabled}
      className={cn(
        'w-full rounded border px-3 py-2 text-sm transition-colors focus:outline-none',  // base
        {
          'border-red-400 bg-red-50 focus:ring-red-300': Boolean(error),   // error state
          'border-green-400 bg-green-50 focus:ring-green-300': success,    // success state
          'border-gray-300 bg-white': !error && !success,                  // neutral state
          'cursor-not-allowed opacity-50': disabled,                       // disabled state
        },
      )}
    />
  );
}


// Pattern 3: Inline styles for runtime-computed values
interface ProgressBarProps {
  percent: number;          // 0–100
  color?: string;           // user-selected color, not a static class
}

function ProgressBar({ percent, color = '#3b82f6' }: ProgressBarProps) {
  const clamped = Math.min(100, Math.max(0, percent));

  return (
    <div className="h-3 w-full overflow-hidden rounded-full bg-gray-200">
      <div
        className="h-full rounded-full transition-all duration-300"
        style={{
          width: `${clamped}%`,   // dynamic: cannot be a Tailwind class
          backgroundColor: color, // dynamic: user-picked color
        }}
      />
    </div>
  );
}


// Combining className and inline styles (correct pattern)
function Avatar({ src, size = 40, isOnline }: { src: string; size?: number; isOnline: boolean }) {
  return (
    <div className="relative inline-block">
      <img
        src={src}
        alt="User avatar"
        className="rounded-full object-cover"
        style={{ width: size, height: size }}  // size is a runtime number
      />
      <span
        className={cn(
          'absolute bottom-0 right-0 block h-3 w-3 rounded-full border-2 border-white',
          isOnline ? 'bg-green-400' : 'bg-gray-400',  // className for boolean state
        )}
      />
    </div>
  );
}

The Avatar example shows the correct combination: className handles all boolean conditions (isOnline), while inline styles handle the numeric size value that cannot be expressed as a static Tailwind class. Mixing the two correctly means never using inline styles where a class would work.


Tradeoffs

ApproachBest forAvoid when
className with ternarySingle boolean conditionThree or more simultaneous conditions
cn() with object syntaxMultiple independent conditionsPure static classes with no conditions
Inline stylesRuntime-computed values (numbers, dynamic colors)Static or boolean conditions

What Interviewers Actually Check

  • Whether you know className with ternary for a simple boolean condition
  • Whether you know cn() or clsx for multiple simultaneous conditions
  • Whether you know that inline styles are for runtime-computed values, not for static conditionals
  • Whether you can explain why string concatenation for class names is error-prone
  • Whether you set a stable base className and add conditional classes on top of it

Follow-Up Questions

  1. How does tailwind-merge resolve conflicting Tailwind classes and why is it necessary when merging classes from props with internal classes?
  2. How would you apply a conditional animation class that runs once, like a flash on update, and then removes itself?
  3. How would you implement a theme switcher where the root element gets a dark class based on user preference?
  4. How does CSS Modules differ from Tailwind for conditional styling, and when would you prefer one over the other?
  5. What is CSS-in-JS (Styled Components, Emotion) and how does it compare to the className approach for conditional styles?

Common Candidate Mistakes

  • Reaching for inline styles for every conditional style, which bypasses the Tailwind utility system and makes styles harder to audit
  • Manually concatenating class name strings: 'btn ' + (active ? 'btn-active' : '') produces a leading space if the base string is wrong, and does not resolve Tailwind conflicts
  • Applying the conditional style through a useEffect that calls element.className = ... directly on the DOM instead of using state-driven className
  • Not knowing the cn() utility and writing deeply nested ternaries in className for multiple conditions

Interview Readiness Checklist

Before you leave this question, make sure you can answer:

  • Can you apply a conditional class name using ternary in className for a single boolean?
  • Can you use cn() to merge base classes with multiple independent conditional classes?
  • Can you apply inline styles correctly for runtime-computed values like a width percentage?
  • Can you explain when to use className vs inline styles?
  • Can you handle multiple simultaneous conditions on one element cleanly?

Summary

React applies conditional styles through the className prop or inline style object. For a single boolean condition, a ternary in className is the clearest pattern: a base set of classes for the default state, a different set for the conditional state. For multiple independent conditions, the cn() utility (backed by clsx and tailwind-merge) accepts any mix of strings and condition objects and returns a clean, merged class string with Tailwind conflicts resolved.

Inline styles are for values that cannot be expressed as static CSS classes because they are computed at runtime from data: a progress bar width from a number, a background color from a user input, a transform from mouse position. Using inline styles for boolean conditions (active, error, disabled) is a common mistake that bypasses the Tailwind system and makes styles harder to inspect.

The correct pattern is: one stable className with all boolean conditions handled through cn() or ternary, and style={{}} added only for the numeric or string values that are truly dynamic. Conditional styles should always be driven by React state or props, never by direct DOM manipulation in useEffect.

Frequently Asked Questions

Should I use inline styles or className for conditional styles in React?

Prefer className with ternary or the cn()/clsx utility for most conditional styling. Inline styles are appropriate for values that are truly dynamic at runtime (colors from user input, pixel values from calculations) and cannot be represented as static CSS classes.

Advertisement


Stay Updated

Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.

Advertisement