Home / Articles / React JSX Looks Like HTML — It Compiles to JavaScript

This article is published in English.

React JSX Looks Like HTML — It Compiles to JavaScript

JSX is JavaScript markup: one root, closed tags, camelCase attributes, and curly braces for expressions — with the classic rules beginners hit first.

1208 words

JSX looks like HTML in a component file, but it compiles to JavaScript — and that fact explains almost every syntax rule.

Earlier chapters often treated the markup a component returns — for example <h1>Hello</h1> — as informal "markup." The real name is JSX. The goal here is simple: treat JSX as JavaScript that happens to look like HTML. Once that is clear, most of the surface syntax stops feeling arbitrary.

Why JSX — markup and logic together

Classic front-end work kept structure in HTML files and behavior in JavaScript files. A button's look and its click handler are hard to separate in practice. React keeps both inside one unit — the component — so it needs a way to write markup inside JavaScript. That syntax is JSX.

Visually it is close to HTML. Because the compiler turns JSX into JavaScript, the rules are stricter than HTML's. Start with the three rules that catch beginners first.

The three rules of JSX

Rule 1 — one root

A component may return only one JSX tree. Several sibling elements must sit under a single parent.

// 🔴 two siblings side by side — error
return (
  <h1>Title</h1>
  <p>Content</p>
);

// ✅ wrapped in one
return (
  <div>
    <h1>Title</h1>
    <p>Content</p>
  </div>
);

If an extra <div> would clutter the DOM, wrap siblings in a Fragment (<>...</>).

return (
  <>
    <h1>Title</h1>
    <p>Content</p>
  </>
);

Why one root? JSX becomes a JavaScript value. A function cannot return two values at once without packaging them; the same idea applies to JSX. The "this is JS, not HTML" framing is what makes the rule make sense.

Rule 2 — close every tag

HTML allows some tags such as <img> or <br> without a closer. In JSX every tag closes. Tags with no children use the self-closing form />.

// 🔴 an HTML habit — error
<img src="logo.png">
<input type="text">

// ✅ self-closing
<img src="logo.png" />
<input type="text" />

Rule 3 — camelCase attributes

JSX attributes become keys on a JavaScript object, so names follow JavaScript conventions (camelCase), not raw HTML names. Two reserved-word collisions show up constantly:

  • class → className (class is reserved in JavaScript)
  • for → htmlFor (for is reserved too)
// 🔴 straight from HTML
<div class="card">
  <label for="email">Email</label>
</div>

// ✅ the JSX way
<div className="card">
  <label htmlFor="email">Email</label>
</div>

The same mapping turns onclick into onClick and tabindex into tabIndex. Memorizing the full list is unnecessary: editor completion and TypeScript flag wrong names quickly.

Curly braces {} — JavaScript inside JSX

This is where JSX stops being a static template. Because JSX is JavaScript, curly braces {} embed a JavaScript value in place. HTML can only hold fixed text; JSX can evaluate expressions.

function Greeting() {
  const name = 'Jane';
  return <h1>Hello, {name}!</h1>; // inside the braces is JavaScript
}

{name} is replaced by the value of name ('Jane'). Braces accept any expression — code that evaluates to a value — not only bare variables.

function Bill() {
  const price = 12000;
  const count = 3;
  return (
    <div>
      <p>Unit price: {price} won</p>
      <p>Quantity: {count}</p>
      <p>Total: {price * count} won</p>          {/* arithmetic works too */}
      <p>{new Date().getFullYear()} receipt</p>  {/* function calls work too */}
    </div>
  );
}

Braces also work in attribute values. In that position you omit quotes around the braces.

function Avatar() {
  const user = { name: 'Jane Kim', imageUrl: '/me.png' };
  return <img src={user.imageUrl} alt={user.name} />;
}
  • src="..." — quotes mean a literal string.
  • src={...} — braces mean a JavaScript value.

Only expressions are allowed

Inside braces you may place an expression that produces a value. You may not place statements such as if, for, or const.

// 🔴 an if statement isn't an expression, so this fails
<p>{if (count > 0) 'in stock'}</p>

// ✅ the ternary operator is an expression, so it works
<p>{count > 0 ? 'in stock' : 'out of stock'}</p>

Conditional UI with ternaries, &&, and related patterns belongs in a dedicated conditional-rendering chapter. For now the working rule is: only value-producing code goes inside curly braces.

Double braces {{ }} — an object inside braces

The {{ }} pattern that appears often in JSX is not a separate language feature. It is a JavaScript object { } nested inside the outer embedding braces. Inline styles are the usual example.

// outer { } = "I'm putting JavaScript here"
// inner { } = the object itself
<div style={{ color: 'tomato', fontSize: 20 }}>styled</div>

HTML would use a string such as style="color: tomato". JSX passes an object, so property names are camelCase (fontSize rather than font-size) and values are typically strings or numbers. If the double braces look opaque, split them into a named variable first.

const cardStyle = { color: 'tomato', fontSize: 20 };
<div style={cardStyle}>styled</div>   // exactly the same as above

The outer brace means "insert a JavaScript value here"; the inner object is that value.

How to write comments

Comments inside JSX also use braces: a JavaScript block comment wrapped in {}.

return (
  <div>
    {/* this is a comment inside JSX */}
    <h1>Title</h1>
  </div>
);

Putting it all together

The rules above fit in one product-card component.

// src/ProductCard.tsx
function ProductCard() {
  const product = {
    name: 'Mechanical Keyboard',
    price: 89000,
    inStock: true,
    imageUrl: "https://picsum.photos/200/300",
  };

  return (
    <div className="card" style={{ padding: 16 }}>
      {/* image: self-closing + brace attributes */}
      <img src={product.imageUrl} alt={product.name} />

      <h2>{product.name}</h2>
      <p>Price: {product.price.toLocaleString()} won</p>

      {/* show stock with an expression (ternary) — more in the next chapter */}
      <p>{product.inStock ? 'In stock' : 'Out of stock'}</p>
    </div>
  );
}

export default ProductCard;

That example uses all three structural rules (single root, closed tags, className) and every common brace pattern (text interpolation, attributes, arithmetic, ternary, style object).

Wrapping up

  • JSX is JavaScript, not HTML — which is why the syntax is stricter than HTML.
  • Three structural rules: wrap siblings in one root (a Fragment is enough), close every tag, and use camelCase attributes (class→className, for→htmlFor).
  • Curly braces {} hold a JavaScript expression, in text or in attributes.
  • Only value-producing expressions belong in braces — not statements such as if or for.
  • {{ }} is not new syntax; it is an object inside braces. Inline styles are the classic case.

The natural next step is feeding data into the component from outside — props. Hard-coding a product object inside the component works for a demo; with props the same ProductCard can render many products, and that is where TypeScript typing starts to matter most.

References

  • React docs on markup with JSX
  • React docs on embedding expressions