Skip to content
React rc jsx 4 min read

Attributes & className

JSX looks like HTML, but attributes follow JavaScript’s rules rather than the DOM’s markup syntax. Because JSX compiles to React.createElement calls whose props are plain object keys, React uses the DOM property names—which are camelCased—instead of the HTML attribute names you might expect. Knowing the handful of renames (classclassName, forhtmlFor), how the style object works, and how to spread props will save you from the most common “why isn’t this applying?” moments.

Attributes are props, in camelCase

Every attribute you write in JSX becomes a key on the element’s props object. Multi-word attributes use camelCase to match the DOM property API, so tabindex becomes tabIndex and maxlength becomes maxLength. String values use quotes; anything dynamic uses curly braces to drop into JavaScript.

function SearchBox({ term, onSearch }) {
  return (
    <input
      type="text"
      value={term}
      placeholder="Search…"
      maxLength={50}
      autoFocus
      onChange={(e) => onSearch(e.target.value)}
    />
  );
}

Note value={term} and maxLength={50} use braces because they carry a JavaScript expression, while type="text" uses a literal string.

className instead of class

class is a reserved word in JavaScript, so JSX uses className to set an element’s CSS classes. Pass a string—static, interpolated, or computed—just like any other attribute.

function Badge({ status }) {
  const cls = status === "active" ? "badge badge--green" : "badge badge--gray";
  return <span className={cls}>{status}</span>;
}

For conditional class lists, template literals or a tiny helper keep things readable:

function NavLink({ href, label, isActive }) {
  return (
    <a href={href} className={`nav-link ${isActive ? "is-active" : ""}`}>
      {label}
    </a>
  );
}

Libraries like clsx or classnames are the idiomatic way to build conditional class strings once you have more than one or two toggles—they trim falsy values automatically.

htmlFor and other renames

The <label for="..."> attribute collides with the JavaScript for keyword, so JSX uses htmlFor. A few other DOM-driven renames follow the same pattern.

function Field() {
  return (
    <>
      <label htmlFor="email">Email</label>
      <input id="email" type="email" name="email" />
    </>
  );
}
HTML attributeJSX attributeNotes
classclassNameclass is a reserved word
forhtmlForfor is a reserved word
tabindextabIndexcamelCase DOM property
maxlengthmaxLengthcamelCase DOM property
readonlyreadOnlycamelCase DOM property
onclickonClickevent handlers are camelCased
aria-labelaria-labelaria-* and data-* stay as-is

aria-* and data-* attributes are the exception: they keep their dashed, lowercase form because they map to real HTML attributes, not DOM properties.

Boolean attributes

HTML treats the mere presence of disabled or checked as true. In JSX, pass an explicit boolean. Writing the bare attribute name is shorthand for ={true}.

function SubmitButton({ isSubmitting }) {
  return (
    <button type="submit" disabled={isSubmitting}>
      {isSubmitting ? "Saving…" : "Save"}
    </button>
  );
}

disabled (alone) equals disabled={true}, while disabled={false} removes the attribute entirely—exactly what you want for dynamic UI.

The style object

Unlike HTML’s style="..." string, JSX’s style attribute takes a JavaScript object with camelCased CSS properties. You’ll see two braces: the outer pair enters JavaScript, the inner pair is the object literal.

function Highlight({ active }) {
  const style = {
    backgroundColor: active ? "#fef08a" : "transparent",
    padding: "2px 6px",
    borderRadius: 4,
    fontWeight: active ? 600 : 400,
  };
  return <mark style={style}>{active ? "On" : "Off"}</mark>;
}

Bare numbers on length properties (borderRadius: 4) are treated as pixels. Use string values when you need other units, like padding: "2px 6px". Reserve inline style for values that depend on state or props—static styling belongs in CSS via className.

Spreading props

When you already have an object of props, the spread operator {...obj} applies each key as an attribute. This is invaluable for forwarding props through a wrapper component.

function TextInput({ label, ...rest }) {
  // `rest` holds id, name, placeholder, onChange, etc.
  return (
    <label>
      {label}
      <input className="input" {...rest} />
    </label>
  );
}

function Form() {
  return (
    <TextInput
      label="Username"
      id="username"
      name="username"
      placeholder="Pick a handle"
    />
  );
}

Order matters: attributes written after a spread override the spread’s values, and attributes before it get overridden. Place explicit overrides last when you want them to win.

// type from `rest` is overridden to "password"
<input {...rest} type="password" />

Avoid blindly spreading unknown props onto DOM elements—you can leak unexpected attributes into the HTML and trigger console warnings. Destructure out what the component owns and spread only the rest.

Best Practices

  • Reach for className and htmlForclass and for are reserved words and will not work.
  • Pass dynamic values in { } and literal strings in quotes; don’t wrap plain strings in braces needlessly.
  • Use explicit booleans for disabled, checked, and readOnly so the attribute toggles cleanly.
  • Keep inline style for state-driven values; move static styling into CSS classes.
  • Build conditional class strings with a helper like clsx once you have multiple toggles.
  • Mind spread order—later attributes override earlier ones and the spread—and avoid spreading unknown props onto DOM nodes.
Last updated June 14, 2026
Was this helpful?