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 (class → className, for → htmlFor), 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
clsxorclassnamesare 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 attribute | JSX attribute | Notes |
|---|---|---|
class | className | class is a reserved word |
for | htmlFor | for is a reserved word |
tabindex | tabIndex | camelCase DOM property |
maxlength | maxLength | camelCase DOM property |
readonly | readOnly | camelCase DOM property |
onclick | onClick | event handlers are camelCased |
aria-label | aria-label | aria-* 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
classNameandhtmlFor—classandforare 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, andreadOnlyso the attribute toggles cleanly. - Keep inline
stylefor state-driven values; move static styling into CSS classes. - Build conditional class strings with a helper like
clsxonce you have multiple toggles. - Mind spread order—later attributes override earlier ones and the spread—and avoid spreading unknown props onto DOM nodes.