Skip to content

React Tutorial Styling React Applications

Conditional Class Names in React

Lesson 84 of 156 2 min read By Deepak Paswan Updated Oct 10, 2026

Runnable lesson · press Try it on the examples to edit and run them here.

Interfaces change appearance with state: a button is active, a field is invalid, a card is selected. In React you do this by choosing class names from state or props, and letting CSS do the styling.

Ternary for one or two cases

App.jsx
JSX
function App() {
  const [dark, setDark] = React.useState(false);

  return (
    <div>
      <style>{`
        .panel { padding: 16px; border-radius: 8px; }
        .panel--light { background: #f4f4f4; color: #111; }
        .panel--dark { background: #222; color: #fff; }
      `}</style>
      <div className={dark ? "panel panel--dark" : "panel panel--light"}>
        Dark mode {dark ? "on" : "off"}
      </div>
      <button onClick={() => setDark(!dark)}>Toggle</button>
    </div>
  );
}

(The <style> element keeps the CSS in the same file for the playground. In a project the CSS lives in its own file.)

A base class plus modifiers

Keep the always-on class, and add modifier classes conditionally. A small helper keeps this readable:

App.jsx
JSX
// Join the truthy names with a space
function cx(...names) {
  return names.filter(Boolean).join(" ");
}

function Tag({ label, selected, onClick }) {
  return (
    <button
      type="button"
      onClick={onClick}
      aria-pressed={selected}
      className={cx("tag", selected && "tag--selected")}
    >
      {selected ? "Selected" : label}
    </button>
  );
}

function App() {
  const [selected, setSelected] = React.useState(false);
  return (
    <div>
      <style>{`
        .tag { padding: 4px 12px; border: 1px solid #1a5276; border-radius: 999px; background: #fff; color: #1a5276; cursor: pointer; }
        .tag--selected { background: #1a5276; color: #fff; }
      `}</style>
      <p>Save</p>
      <Tag label="Select" selected={selected} onClick={() => setSelected(!selected)} />
    </div>
  );
}

cx("tag", false) gives "tag"; cx("tag", true && "tag--selected") gives "tag tag--selected". The popular library clsx (or classnames) offers the same helper with more options, if you install it in a project.

Why not build strings by hand?

App.jsx
JSX
className={"btn " + (primary ? "btn--primary" : "") + " " + (large ? "btn--large" : "")}

This leaves extra spaces and is hard to read. The helper avoids both.

Variants through an object lookup

When a prop has a fixed set of values, map each to its class:

App.jsx
JSX
const variantClass = {
  info: "badge badge--info",
  warning: "badge badge--warning",
  error: "badge badge--error",
};

function Badge({ variant = "info", children }) {
  return <span className={variantClass[variant]}>{children}</span>;
}

function App() {
  return (
    <div style={{ display: "flex", gap: 8 }}>
      <style>{`
        .badge { padding: 2px 10px; border-radius: 999px; font-size: 13px; }
        .badge--info { background: #d6eaf8; color: #154360; }
        .badge--warning { background: #fcf3cf; color: #7d6608; }
        .badge--error { background: #fadbd8; color: #78281f; }
      `}</style>
      <Badge>Info</Badge>
      <Badge variant="warning">Warning</Badge>
      <Badge variant="error">Error</Badge>
    </div>
  );
}

A lookup makes the allowed values obvious, and a missing key shows up as undefined. With TypeScript, the keys can be checked at compile time.

Data attributes as an alternative

Instead of many class names, set a data attribute and style it in CSS. It keeps JSX short, and CSS can combine states:

App.jsx
JSX
function App() {
  const states = ["idle", "loading", "done"];
  const [i, setI] = React.useState(0);
  const status = states[i];

  return (
    <div>
      <style>{`
        .status { padding: 8px 12px; border-radius: 6px; }
        .status[data-status="idle"] { background: #eee; }
        .status[data-status="loading"] { background: #fdebd0; }
        .status[data-status="done"] { background: #d5f5e3; }
      `}</style>
      <p className="status" data-status={status}>Status: {status}</p>
      <button onClick={() => setI((i + 1) % states.length)}>Next</button>
    </div>
  );
}

State that CSS already knows

Do not recreate what CSS can do: :hover, :focus-visible, :disabled, :checked, :has() and :invalid need no React state. Use state only for things CSS cannot know, such as “this card is selected”.

Accessibility reminder

Colour and class changes must not be the only signal. For a toggle, also set aria-pressed or aria-expanded as in the Tag example, so assistive technology knows the state.

Key points

  • Choose class names from state and props; let CSS do the styling.
  • Use a base class plus modifiers, a tiny cx helper or an object lookup.
  • Prefer CSS pseudo-classes over state where possible, and add ARIA attributes for state.