AniUI Academy

JSX and Elements

How JSX compiles to plain JavaScript function calls, the handful of rules that trip up newcomers, and what a React element actually is under the hood.

9 min read

You've now seen JSX in passing — the <button>...</button>-looking syntax inside a component. This lesson is about what it actually is, because "HTML inside JavaScript" is a useful mental model right up until it misleads you.

JSX is not HTML

JSX looks like markup, but it is not interpreted by anything at runtime. A build step — Babel, SWC, or the TypeScript compiler — transforms every JSX tag into a plain JavaScript function call before your code ever ships. This:

const element = <h1 className="title">Hello</h1>;

compiles to something equivalent to this:

const element = React.createElement("h1", { className: "title" }, "Hello");

(Modern tooling actually emits a call to an internal jsx function imported automatically from react/jsx-runtime, which is a small optimization over calling React.createElement directly — but the idea is identical: JSX is sugar for function calls, nothing more.)

That function call returns a plain JavaScript object — a React element — describing what should appear on screen: what kind of tag, what props, what children. It is not a real DOM node. It's more like a lightweight description, cheap to create and throw away, which React later uses to figure out what real DOM nodes to create or update. That's the whole basis for the "virtual DOM" idea you'll go deeper on in a later lesson.

Expressions live inside curly braces

Anywhere you want a JavaScript value inside JSX, wrap it in {}:

function Greeting({ name }) {
  const upper = name.toUpperCase();
  return <p>Hello, {upper}! Today is {new Date().toLocaleDateString()}.</p>;
}

Anything that is a valid JavaScript expression is allowed inside {} — variables, function calls, ternaries, arithmetic. Statements (if, for, variable declarations) are not expressions, so they can't go directly inside {}; that's why conditional rendering in JSX leans on expressions like the ternary operator or &&, which the next lesson covers properly.

One root value

A component has to return one value, and since JSX compiles to one function call per tag, that means one root element:

// Invalid — two sibling elements with no common parent
function Card() {
  return (
    <h2>Title</h2>
    <p>Body</p>
  );
}

Wrapping in a div fixes it, but adds a real DOM node you might not want. The fragment (<>...</> or the explicit <Fragment>...</Fragment>) groups children without adding anything to the DOM at all:

function Card() {
  return (
    <>
      <h2>Title</h2>
      <p>Body</p>
    </>
  );
}

The handful of naming differences

Because JSX attributes become JavaScript object properties, a few HTML attribute names change to avoid colliding with reserved words or existing DOM API names:

  • class becomes className
  • for (on a label) becomes htmlFor
  • tabindex becomes tabIndex — JSX attributes are camelCase in general, matching the DOM property names rather than the HTML attribute names
  • Inline styles are an object, not a string: style={{ color: "red" }} (the outer {} is "this is an expression", the inner {} is the object literal itself)

Everything else reads close enough to HTML that it rarely trips people up.

Self-closing tags are mandatory for empty elements

In HTML, <img src="..."> and <br> don't need a closing slash. In JSX, every element must be explicitly closed, so an element with no children needs the self-closing form:

<img src="/cat.png" alt="A cat" />
<br />

Leaving off the slash on an empty tag is a syntax error, not a lenient warning — the JSX compiler enforces it.

Try it yourself

This component uses an expression, a conditional, and a fragment, all in one small example:

Try it yourself
Loading playground...

What to remember

  • JSX compiles to plain JavaScript function calls — there's no runtime magic, and no JSX left once it's compiled.
  • A React element is a lightweight object describing UI, not a real DOM node.
  • {} switches into JavaScript-expression mode; only expressions are allowed, not statements.
  • A component returns one root value — use a fragment (<>...</>) to group siblings without adding a DOM node.
  • Attribute names are camelCase and match DOM properties (className, htmlFor, onClick), not raw HTML attribute names.

Check yourself

4 questions · pass 3/4 to unlock Components and Props

up to 50
  1. 1.What does JSX actually compile down to?

  2. 2.Why does return <div><p>One</p><p>Two</p></div>; need the outer div (or a fragment)?

  3. 3.Which is the correct way to render a dynamic value inside JSX?

  4. 4.Why is className, not class, the correct JSX attribute for a CSS class?

4 left to answer