Himanshu SharmaSoftware engineer
Let’s talk

React · Lesson 5 of 38

JSX: HTML-like syntax in JavaScript

Learn the rules of JSX in React 19: one root element, fragments, closing tags, className, camelCase attributes, curly braces and inline styles.

18 minGetting startedUpdated 24 October 2026

By the end of this lessonWrite valid JSX, embed JavaScript values in it, and fix the common errors the compiler reports.

JSX is the syntax that lets you write the structure of a component in a form that looks like HTML. It is what makes React code readable, and it is the first thing that feels strange. This lesson gives you the rules in one place, so the error messages stop being mysterious. Every example runs in the editor: change it, break it on purpose and read what the page says.

JSX is JavaScript, not HTML

A tag in JSX is a shorthand for a function call. When the build tool compiles this line:

jsx
const element = <h1 className="title">Hello, {name}!</h1>;

it produces ordinary JavaScript (React 19's automatic runtime, shown here as compiled by Babel):

javascript
const element = _jsxs("h1", {
  className: "title",
  children: ["Hello, ", name, "!"]
});

The call creates a plain JavaScript object, a React element, describing an h1 with a class and three children. React compares such objects to work out what to change in the page. You never need to write these calls yourself. Understanding that JSX is JavaScript explains all the rules below: they exist because the code must still be valid JavaScript underneath.

Rule 1: return one root element

A function can return only one value, so a component must return one element. This fails:

jsx
function Page() {
  return (
    <h1>Title</h1>
    <p>Text</p>
  );
}

Wrap the pair in a parent element, or in a fragment, an empty tag that groups children without adding anything to the page:

jsx
function App() {
  return (
    <>
      <h1>Title</h1>
      <p>Text</p>
    </>
  );
}
export default App;
result
Title
Text

Prefer a fragment when you do not need a wrapper div; it keeps the HTML clean and avoids breaking layouts such as lists and tables. When you return JSX over several lines, wrap it in round brackets ( ... ) so that JavaScript does not stop after return and treat the next line as separate.

Rule 2: close every tag

HTML lets you leave some tags open (<br>, <img>, <input>). JSX does not: every element must be closed, either with a closing tag or, for elements without children, with a self-closing slash.

jsx
const dot = "data:image/svg+xml," + encodeURIComponent(
  '<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40"><circle cx="20" cy="20" r="20" fill="#bba8ff"/></svg>'
);

function App() {
  return (
    <div>
      <label>Name <input type="text" placeholder="Your name" /></label>
      <br />
      <p>Line one<br />Line two</p>
      <img src={dot} alt="A purple dot" width="40" height="40" />
      <hr />
    </div>
  );
}
export default App;
result
Name 
Line one
Line two

Your own components follow the same rule: <Header /> for one without children.

Rule 3: attribute names are camelCase, with a few renames

JSX attributes become JavaScript object properties, so they use camelCase, and words that clash with JavaScript are renamed:

HTML JSX
class className
for (on a label) htmlFor
tabindex tabIndex
onclick onClick
maxlength maxLength
readonly readOnly
autofocus autoFocus
charset charSet

The exceptions are aria-* and data-* attributes, which keep their hyphens: aria-label="Close", data-id="7". If you use the wrong name, React's console warning tells you the right one.

Rule 4: curly braces switch back to JavaScript

Inside JSX, { ... } opens a window into JavaScript. You can put any expression there, in two places: as the content of an element, or as an attribute value.

jsx
function App() {
  const user = { name: "Asha", visits: 3 };
  const avatar = "data:image/svg+xml," + encodeURIComponent(
    '<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40"><circle cx="20" cy="20" r="20" fill="#86dbb4"/></svg>'
  );
  const label = "Profile photo of " + user.name;

  return (
    <div>
      <h1>Hello, {user.name}!</h1>
      <p>You have visited {user.visits} times, which is {user.visits * 7 % 5} mod 5.</p>
      <p>Today is a {new Date(2026, 9, 24).toLocaleDateString("en-GB", { weekday: "long" })}.</p>
      <img src={avatar} alt={label} width="40" height="40" />
    </div>
  );
}
export default App;
result
Hello, Asha!
You have visited 3 times, which is 1 mod 5.
Today is a Saturday.

Points to remember:

  • An expression produces a value: variables, arithmetic, function calls, ternaries, array.map(...). A statement does not: if, for, const x = 1. Statements cannot go inside braces. Compute the value above the return, or use condition ? a : b and && inside.
  • For attributes, use braces instead of quotes: src={avatar}. Write src="avatar" and you get the literal text avatar. Never both: src="{avatar}" is the text {avatar}.
  • Strings, numbers and elements render as content. true, false, null and undefined render nothing. Plain objects cannot be rendered and cause the error "Objects are not valid as a React child"; show a property instead.
  • Values are escaped, so putting user text in braces is safe, as the next example shows.

Text is escaped automatically

If a value contains HTML, React shows it as text rather than running it. This protects you from cross-site scripting (XSS), an attack where someone enters <script> or <img onerror=...> in a form.

jsx
function App() {
  const comment = '<img src=x onerror="alert(1)"> <b>bold?</b>';
  return <p>{comment}</p>;
}
export default App;
result
<img src=x onerror="alert(1)"> <b>bold?</b>

The tags appeared as plain characters, and nothing ran. If you really need to insert trusted HTML (for example, from your own editor), there is an escape hatch whose name is a warning: dangerouslySetInnerHTML={{ __html: html }}. Never use it with text from users.

Inline styles are objects

The style attribute takes a JavaScript object, not a string. That is why you see double braces: the outer pair opens JavaScript, the inner pair is the object. Property names are camelCase, and numbers get px added for sizes.

jsx
function App() {
  const box = {
    backgroundColor: "#ffd45c",
    padding: 12,            // 12px
    borderRadius: 8,
    fontSize: "1.1rem",
  };
  return (
    <div>
      <p style={box}>A styled paragraph</p>
      <p style={{ color: "crimson", fontWeight: "bold" }}>Inline object</p>
    </div>
  );
}
export default App;
result
A styled paragraph
Inline object

Inline styles are fine for values that change with data, such as a progress bar's width. For everything else, use CSS classes with className, covered in Styling React apps.

Comments, conditionals and whitespace

Comments inside JSX need braces: {/* a comment */}. JSX also collapses whitespace: line breaks and indentation between tags are removed, and spaces inside text are trimmed at line edges. To keep a space between two inline pieces that sit on separate lines, add {" "}.

jsx
function App() {
  return (
    <p>
      {/* this comment is not rendered */}
      First line,
      continues here.{" "}
      <strong>bold</strong>
    </p>
  );
}
export default App;
result
First line, continues here. bold

Components are tags with capital letters

JSX treats a lower-case tag (<div>, <button>) as an HTML element and a capitalised tag (<Greeting />) as a component you defined. That is why component names must start with a capital letter. The next lesson builds your first component.

Reading error messages

Message Likely cause
Adjacent JSX elements must be wrapped in an enclosing tag two root elements; add a fragment
Unterminated JSX contents or Expected corresponding JSX closing tag an element was not closed
Invalid DOM property class. Did you mean className? (console warning) class instead of className
Objects are not valid as a React child you put an object or a Date directly in braces
x is not defined a variable used in braces that was never declared or imported
Unexpected token a statement such as if inside braces, or a missing bracket

Common mistakes

  • Writing class and for instead of className and htmlFor.
  • Returning multiple root elements without a fragment.
  • Forgetting to close <img>, <input> or <br>.
  • Putting quotes around braces: value="{count}".
  • Writing style="color: red"; use an object.
  • Putting if or for statements inside braces instead of using expressions (? :, &&, map).
  • Lower-case component names such as <greeting />, which React treats as an unknown HTML tag.

Exercise

  1. In the first editor, add a third paragraph that shows 2 + 3 computed inside braces.
  2. Break the second editor on purpose: remove the / from <br />. Read the message, then fix it.
  3. In the style example, add textAlign: "center" to box, and make the second paragraph's colour come from a const color = "teal" variable.
  4. Put <b>hello</b> in a variable and render it in braces. What do you see, and why is that the safe behaviour?

Key points

  • JSX looks like HTML but is JavaScript: each tag becomes a function call that creates a React element.
  • A component returns one root element; use a fragment (<>...</>) to group elements without an extra wrapper.
  • Close every tag, use className instead of class, and write attributes in camelCase (onClick, tabIndex, htmlFor).
  • Curly braces put any JavaScript expression into JSX, as text or as an attribute value; style takes an object, not a string.

Check yourself

1. Which is valid JSX for a CSS class?
2. What is wrong with a component that returns two sibling elements, <h1>A</h1><p>B</p>?
3. How do you set an inline style for red text in JSX?
4. What happens to a string you put in curly braces, like {userInput}?