What Is JSX and How Does It Work in React?

Updated

JSX is a syntax extension for JavaScript that lets you describe UI with markup that looks like HTML. It is not HTML and it is not a string — it compiles into regular JavaScript function calls, usually React.createElement(...), which produce the element objects React uses to build the DOM. You should use JSX when you write React components and want the structure of your UI to stay readable next to the logic that drives it.

JSX compiles to function calls

When you write:

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

A compiler (Babel, or the automatic JSX runtime in modern React) turns it into something like:

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

At runtime there is no HTML parsing step. React.createElement returns a plain object describing the element — its type, props, and children. React reads that object tree and reconciles it against the real DOM. This is why JSX expressions are values: you can assign them to variables, return them from functions, pass them as props, and store them in arrays.

With the newer automatic runtime, you no longer need React in scope just to use JSX, but the compiled output still creates element objects the same way.

Key syntax differences from HTML

JSX looks like HTML but follows JavaScript rules. The differences that trip people up most often:

HTML JSX Why
class="box" className="box" class is a reserved word in JavaScript
for="name" htmlFor="name" for is a reserved word
onclick="..." onClick={handler} Events are camelCase and take functions, not strings
tabindex tabIndex Attributes are camelCase
<img src="x.png"> <img src="x.png" /> Every tag must be closed
style="color:red" style={{ color: "red" }} style takes an object, not a string

Attribute names are camelCase because they map to DOM property names, not HTML attribute names. The style prop is a special case: it expects a JavaScript object with camelCase CSS properties.

Embedding expressions with curly braces

Curly braces switch from markup into JavaScript:

const name = "Ada";
const element = <p>Hello, {name}!</p>;

Anything that evaluates to a value can go inside the braces — variables, function calls, arithmetic, ternaries. You cannot put statements like if or for inside braces, only expressions. That constraint is why conditional rendering and lists use expressions rather than blocks.

Conditional rendering

Because JSX is expression-based, you branch with operators instead of if statements:

{isLoggedIn ? <Dashboard /> : <Login />}
{error && <ErrorMessage text={error} />}

The && pattern renders the right side only when the left side is truthy. Watch out for numbers: {count && <Badge />} renders 0 when count is 0, because 0 is falsy but still a valid React child. Use {count > 0 && <Badge />} to avoid that.

Lists and keys

Render arrays of elements with .map():

<ul>
  {items.map((item) => (
    <li key={item.id}>{item.name}</li>
  ))}
</ul>

The key prop gives each element a stable identity so React can match items between renders. Use a value that is unique and stable — an ID from your data is ideal. Array indices work only when the list never reorders, inserts, or deletes, because index keys cause React to reuse the wrong DOM nodes and can produce stale state or visual glitches.

Fragments

A component can return only one root element. To return several siblings without adding a wrapper <div>, use a fragment:

return (
  <>
    <Header />
    <Main />
  </>
);

The short syntax <>...</> is shorthand for <React.Fragment>. Use the long form when you need to pass a key to the fragment itself.

Common pitfalls

  • Adjacent elements without a wrapper. return <A /><B />; is invalid — wrap them in a fragment or a parent element.
  • Reserved words as props. class, for, and similar names must become className, htmlFor, and so on.
  • Unclosed tags. <br>, <img>, and <input> must be self-closed as <br />, <img />, <input />.
  • Missing React import in older setups. Before the automatic runtime, every file using JSX needed import React from "react";. If you see "React is not defined," that import is likely missing.
  • Comments. Inside JSX, use {/* comment */} — plain // or /* */ will render as text or break parsing.

Where JSX fits with SVG

JSX is not limited to HTML elements. You can write SVG directly in JSX, and the same camelCase rule applies to SVG attributes — for example stroke-width becomes strokeWidth and fill-rule becomes fillRule. Tools like svg2jsx.com exist specifically to convert raw SVG markup into valid JSX, handling those attribute renames and self-closing tags for you. That is useful when you copy an icon from a design tool and want to paste it straight into a component.

Once you internalize that JSX is JavaScript that produces element objects, the rules stop feeling arbitrary: camelCase attributes, expression-only braces, and single-root returns all follow from the fact that you are writing function calls, not markup.