JSX Attributes: className and htmlFor Guide
JSX attributes differ from HTML attributes because JSX is transpiled into JavaScript objects, and certain words are reserved in JavaScript. Understanding these differences is essential for writing error-free React code. This guide covers the most common attribute changes, why they exist, and when exceptions apply.
Key Takeaways
classbecomesclassName— avoids conflict with the JavaScriptclasskeywordforbecomeshtmlFor— avoids conflict with the JavaScriptforloop syntax- camelCase is standard — multi-word attributes like
tabindexbecometabIndex data-*andaria-*are exceptions — these remain hyphenated per web standards- Always validate with ESLint — a properly configured linter catches these mistakes automatically
Why Do JSX Attributes Differ from HTML?
JSX is JavaScript. When you write JSX, the transpiler converts it into JavaScript function calls with a props object. Because JavaScript has reserved words and identifier rules, attribute names must comply with JavaScript naming conventions.
// This JSX:
<div className="profile" />
// Becomes this JavaScript:
{
className: "profile"
}
If you used class or for as property names, you would trigger a syntax error. HTML attributes that conflict with JavaScript keywords or that contain hyphens must be renamed.
The Two Most Common Attribute Changes
class Becomes className
In HTML, the class attribute applies CSS styling to elements. In React, you must use className instead because class is a reserved keyword in JavaScript used for class declarations.
Why the change exists: When JSX transpiles to JavaScript, class would conflict with the class MyClass {} syntax.
How React handles it: React automatically converts className back to class when rendering the final HTML to the DOM.
// React component using className
import React from 'react';
import './styles.css'; // CSS with: .active { color: green; }
function StatusMessage() {
const isActive = true;
return (
<div className={isActive ? 'active' : 'inactive'}>
Current Status
</div>
);
}
export default StatusMessage;
When rendered to the DOM, this produces:
<div class="active">Current Status</div>
for Becomes htmlFor
In HTML, the <label> element's for attribute associates the label with a form input, improving accessibility. In React, you must use htmlFor because for is a reserved keyword used for loops.
Why the change exists: When JSX transpiles to JavaScript, for would conflict with the for (let i = 0; i < 10; i++) loop syntax.
How React handles it: React automatically converts htmlFor to for in the rendered HTML, creating the proper accessibility association.
// React form using htmlFor
import React from 'react';
function LoginForm() {
return (
<form>
<label htmlFor="usernameInput">Username:</label>
<input type="text" id="usernameInput" />
</form>
);
}
export default LoginForm;
When rendered to the DOM, this produces:
<form>
<label for="usernameInput">Username:</label>
<input type="text" id="usernameInput" />
</form>
Clicking the label will now correctly focus the input, just as it would in plain HTML.
The camelCase Convention for Multi-Word Attributes
Beyond reserved words, HTML attributes containing hyphens must be converted to camelCase in JSX. This is because hyphens are not valid in JavaScript identifiers (the hyphen would be interpreted as subtraction).
HTML to JSX Conversion Examples:
| HTML Attribute | JSX Attribute | Use Case |
|---|---|---|
accept-charset | acceptCharset | Input character encoding |
http-equiv | httpEquiv | HTTP header directives |
stroke-width | strokeWidth | SVG stroke styling |
tabindex | tabIndex | Keyboard tab order |
// React component demonstrating camelCase
import React from 'react';
function CustomInput() {
return (
<input
type="text"
tabIndex={0}
placeholder="I have a tab index"
maxLength={50}
acceptCharset="UTF-8"
/>
);
}
export default CustomInput;
Exceptions: data-* and aria-* Attributes
Accessibility and custom data attributes are exceptions to the camelCase rule. React preserves data-* (custom data) and aria-* (accessibility) attributes exactly as written in HTML, with hyphens intact.
Why these exceptions exist: These attribute types have standardized formats in the HTML specification and should remain exactly as defined for proper accessibility and data semantics.
// Accessibility and custom data attributes remain hyphenated
import React from 'react';
function AccessibleComponent() {
const hasError = true;
return (
<div
data-testid="user-greeting"
data-user-id="12345"
aria-live="polite"
aria-atomic="true"
aria-label="Status notification"
role="status"
>
{hasError ? 'An error occurred.' : 'Data loaded successfully.'}
</div>
);
}
export default AccessibleComponent;
React preserves these attributes exactly, passing them directly to the DOM without transformation.
Best Practices for JSX Attributes
Use ESLint with the React plugin: Configure ESLint with eslint-plugin-react to catch attribute mistakes like using class instead of className or for instead of htmlFor. Most modern React projects include this automatically.
Refer to the official React docs: When you are unsure about a specific attribute, consult the React documentation on DOM elements. It provides a comprehensive list of supported attributes and their JSX equivalents.
Maintain consistency: Always use camelCase for multi-word attributes unless they are data-* or aria-* attributes. This consistency makes your code more maintainable and reduces errors.
Test with your linter: Run your linter before committing code. Most IDEs (VSCode, WebStorm) highlight attribute errors in real time.
Frequently Asked Questions
What happens if I accidentally use class instead of className?
Your React code will fail to transpile. The JavaScript parser treats class as a reserved keyword, so using it as a property name causes a syntax error. Your build will fail before the code reaches the browser. Always use className in JSX.
Can I use for in a React component?
Only in JavaScript logic outside JSX. The for keyword works in regular JavaScript (e.g., for loops), but you cannot use it in JSX elements. For form labels, always use htmlFor.
Why does React convert camelCase back to kebab-case in some cases?
For data-* and aria-* attributes, React does not convert them—it passes them to the DOM as written. For other camelCase attributes like acceptCharset, React converts them to their HTML equivalents (accept-charset) when rendering. This is because the DOM API uses camelCase (e.g., element.acceptCharset), while HTML attributes use kebab-case.
Are SVG attributes treated differently?
SVG attributes follow the same camelCase rules as HTML attributes. For example, stroke-width becomes strokeWidth in JSX. However, some SVG attributes like xlink:href have special handling in React; refer to the React docs for SVG-specific guidance.
What is the difference between data-* attributes and regular JSX props?
data-* attributes are attached to the DOM element and are accessible via JavaScript's element.dataset API. Regular JSX props are React-specific and do not appear in the HTML unless you explicitly render them as attributes. Use data-* when you need the attribute to be part of the final HTML.