JSX Attributes & className
JSX looks like HTML, so it is natural to assume its attributes work the same way. They almost do — but JSX attributes are JavaScript properties, not HTML attributes. That distinction drives a handful of important differences that every React developer must memorise.
Attributes Are camelCase in JSX
HTML attribute names are case-insensitive and use lowercase or hyphenated names (tabindex, maxlength, crossorigin). JSX attribute names map to the DOM property names, which are camelCase:
// HTML
<input tabindex="0" maxlength="100" autocomplete="off" />
// JSX equivalent
<input tabIndex={0} maxLength={100} autoComplete="off" />className Instead of class
The most common trip-up for developers coming from HTML: in JSX you must use className, not class. The reason is that class is a reserved keyword in JavaScript (used for ES6 classes), so it cannot be used as a property name in early JavaScript versions:
// ❌ HTML class — syntax warning in JSX <div class="container"> <p class="text-muted">Hello</p> </div> // ✅ JSX className <div className="container"> <p className="text-muted">Hello</p> </div>
htmlFor Instead of for
The HTML for attribute (used on <label> to associate it with an
input) becomes htmlFor in JSX for the same reason — for is a
reserved keyword in JavaScript:
// ❌ HTML <label for="email">Email address</label> <input id="email" type="email" /> // ✅ JSX <label htmlFor="email">Email address</label> <input id="email" type="email" />
Event Handlers Are camelCase Props
HTML uses lowercase attribute names for event handlers (onclick, onchange, onsubmit). In JSX they are camelCase and accept a function reference, not a string of JavaScript code:
// HTML — handler as an inline string of code
<button onclick="handleClick()">Click me</button>
// JSX — handler as a function reference
function MyButton() {
function handleClick() {
alert('Button clicked!')
}
return <button onClick={handleClick}>Click me</button>
}
// Arrow function inline (fine for simple cases)
<button onClick={() => console.log('clicked')}>Click</button>The style Attribute Takes an Object
In HTML, style is a CSS string. In JSX, style accepts a JavaScript object where the CSS property names are camelCase:
// HTML
<p style="color: red; font-size: 16px; background-color: #fff">Text</p>
// JSX — style object with camelCase properties
<p style={{ color: 'red', fontSize: '16px', backgroundColor: '#fff' }}>
Text
</p>
// Dynamic styles are easy with an object
function ProgressBar({ percent }) {
return (
<div
style={{
width: `${percent}%`,
height: '8px',
backgroundColor: percent === 100 ? '#22c55e' : '#3b82f6',
transition: 'width 0.3s ease',
}}
/>
)
}Passing Strings vs Expressions
String attribute values can be passed with either quotes or curly braces. All other types (numbers, booleans, objects, arrays, functions) must use curly braces:
// String — quotes and braces are both valid, quotes are more common
<img src="/logo.png" alt="Company logo" />
<img src={'/logo.png'} alt={'Company logo'} />
// Number — must use braces
<input maxLength={100} tabIndex={0} />
// Boolean — just the attribute name is shorthand for true
<input disabled /> // same as disabled={true}
<input disabled={false} /> // explicitly false
// Object
<div style={{ margin: '0 auto' }} />
// Function
<button onClick={handleClick} />
// Expression
<img src={user.avatarUrl} alt={`${user.name}'s avatar`} />Complete Attribute Differences Reference
HTML Attribute | JSX Attribute | Notes |
|---|---|---|
class | className | Reserved JS keyword |
for | htmlFor | Reserved JS keyword |
tabindex | tabIndex | camelCase DOM property |
maxlength | maxLength | camelCase DOM property |
minlength | minLength | camelCase DOM property |
readonly | readOnly | camelCase DOM property |
autofocus | autoFocus | camelCase DOM property |
autocomplete | autoComplete | camelCase DOM property |
crossorigin | crossOrigin | camelCase DOM property |
enctype | encType | camelCase DOM property |
contenteditable | contentEditable | camelCase DOM property |
spellcheck | spellCheck | camelCase DOM property |
onclick | onClick | Event — camelCase + function prop |
onchange | onChange | Event — camelCase + function prop |
onsubmit | onSubmit | Event — camelCase + function prop |
onkeydown | onKeyDown | Event — camelCase + function prop |
style="color:red" | style={{ color: "red" }} | Style is a JS object |
value (HTML) | defaultValue | Uncontrolled input default |
checked (HTML) | defaultChecked | Uncontrolled checkbox default |
Special Case: data-* and aria-* Attributes
Custom data-* and aria-* attributes are the exception to the camelCase rule — they keep their original hyphenated names in JSX because they are not DOM property names:
// data-* and aria-* keep their hyphenated names
<button
data-testid="submit-btn"
aria-label="Submit the form"
aria-disabled={isSubmitting}
onClick={handleSubmit}
>
Submit
</button>
<div
role="progressbar"
aria-valuenow={progress}
aria-valuemin={0}
aria-valuemax={100}
/>Spreading Props
JSX supports the spread operator to pass all properties of an object as individual props. This is useful for wrapping native HTML elements while forwarding their standard attributes:
// Pass all props of inputProps to <input>
function TextInput({ label, className, ...inputProps }) {
return (
<label>
{label}
<input
className={`input ${className ?? ''}`}
{...inputProps}
/>
</label>
)
}
// Usage — all standard <input> attributes work
<TextInput
label="Username"
type="text"
maxLength={50}
autoComplete="username"
required
/>Summary
JSX attributes map to DOM properties, not HTML attributes — they are camelCase
Use
className(notclass) andhtmlFor(notfor)Event handler props are camelCase and accept function references:
onClick={fn}The
styleprop takes a JavaScript object with camelCase property namesString values can use quotes; all other types (numbers, booleans, objects, functions) must use
{}data-*andaria-*attributes keep their hyphenated namesThe spread operator
{...props}forwards all props at once