ReactJSX Attributes & className

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:

JSX
// 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:

JSX
// ❌ 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>
Note
React will actually render `class` in many environments and show a warning, but always use `className` in JSX. The TypeScript JSX types enforce this at compile time.
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:

JSX
// ❌ 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:

JSX
// 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>
Warning
Never call the function when passing it as a prop: `onClick={handleClick}` is correct; `onClick={handleClick()}` calls the function immediately during render and passes its return value — almost certainly not what you want.
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:

JSX
// 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',
      }}
    />
  )
}
Tip
The double curly braces `{{}}` are not a special JSX syntax — the outer `{}` means "embed a JavaScript expression", and the inner `{}` is a plain JavaScript object literal. You can also pass a pre-defined style object: `const style = { color: 'red' }` then `<p style={style}>`.
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:

JSX
// 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:

JSX
// 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:

JSX
// 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
/>
Warning
Spread props can make it hard to see which props a component actually uses, and can accidentally forward unknown attributes to native DOM elements, causing React warnings. Use spread thoughtfully and prefer explicit props in most cases.
Summary
  • JSX attributes map to DOM properties, not HTML attributes — they are camelCase

  • Use className (not class) and htmlFor (not for)

  • Event handler props are camelCase and accept function references: onClick={fn}

  • The style prop takes a JavaScript object with camelCase property names

  • String values can use quotes; all other types (numbers, booleans, objects, functions) must use {}

  • data-* and aria-* attributes keep their hyphenated names

  • The spread operator {...props} forwards all props at once