How to Change Styles Based on a Condition in React
Advertisement
🧩 Scenario
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
| Approach | Best for | Avoid when |
|---|---|---|
| className with ternary | Single boolean condition | Three or more simultaneous conditions |
| cn() with object syntax | Multiple independent conditions | Pure static classes with no conditions |
| Inline styles | Runtime-computed values (numbers, dynamic colors) | Static or boolean conditions |
What Interviewers Actually Check
- Whether you know
classNamewith ternary for a simple boolean condition - Whether you know
cn()orclsxfor 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
- How does
tailwind-mergeresolve conflicting Tailwind classes and why is it necessary when merging classes from props with internal classes? - How would you apply a conditional animation class that runs once, like a flash on update, and then removes itself?
- How would you implement a theme switcher where the root element gets a
darkclass based on user preference? - How does CSS Modules differ from Tailwind for conditional styling, and when would you prefer one over the other?
- 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
useEffectthat callselement.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.
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