Blog

Part 1 · JSX in React: Writing HTML-like Code in JavaScript

JSX lets you write HTML-like tags inside JavaScript. Learn what JSX turns into, how curly braces work, the three rules of JSX, and common mistakes. Every example runs in the page.

React is a JavaScript library for building web pages that change as people use them. A library is code that other people wrote for you to use. React’s main idea is simple. You describe what the page should look like, and React makes the page match.

To describe the page, React code uses JSX. JSX looks like HTML, but you write it inside JavaScript. This first part is about JSX: what it is, what it turns into, and its few rules.

You need to know a little JavaScript before you start: variables, functions and objects. You don’t need to install anything. Every example runs here, in the page.

How to use the examples

Some code blocks in this series have buttons under them.

  • Run shows what the code draws, in a box under the code.
  • Edit lets you change the code. Then press Run again.
  • Reset puts the code back the way it was.

Under the result there is sometimes a Console area. The console is where a program prints notes for the programmer. React prints its warnings there too.

Change things and break things. You can’t harm anything. The code runs in its own closed box.

Try this first

Read this code, but don’t press Run yet.

export default function App() {
  const name = 'Ana'
  const apples = 3

  return (
    <div>
      <h1>Hello, {name}!</h1>
      <p>You have {apples} apples.</p>
      <p>Two more makes {apples + 2}.</p>
    </div>
  )
}

Make a guess. Will the last line show {apples + 2}, or will it show a number?

Now press Run.

The page shows “Two more makes 5.” The part inside { } was not shown as text. It ran as JavaScript, and its answer went onto the page.

Press Edit and change 'Ana' to your own name. Run it again.

What you just wrote

The function App returns something that looks like HTML: <div>, <h1> and <p> tags. That is JSX.

A browser can’t read JSX. So before your code reaches the browser, a tool turns each JSX tag into a normal JavaScript function call. In a real project, a tool called Vite does this for you. Here, the playground does it.

Let’s follow one tag all the way to the page.

1. You write JSX. It looks like HTML, but it is inside JavaScript. 2. A tool turns the JSX into a normal JavaScript function call. 3. When that call runs, it makes a plain object. React calls it an element. 4. React reads the element and puts a real <h1> on the page. <h1 className="greeting">Hello!</h1> _jsx("h1", { className: "greeting", children: "Hello!" }) { type: "h1", props: { className: "greeting", children: "Hello!" } } Hello!the browser page now has <h1 class="greeting">Hello!</h1>

From JSX to the page. Press play, or step through it with the arrows.

Here are the same four steps in words.

  1. You write JSX: <h1 className="greeting">Hello!</h1>.
  2. A tool turns it into a function call: _jsx("h1", { className: "greeting", children: "Hello!" }).
  3. When that call runs, it makes a plain JavaScript object. React calls this object an element. It holds the tag’s name (type) and everything else about the tag (props). An attribute is the extra information inside a tag, like className="greeting". Props is short for properties: the attributes, plus whatever is between the opening and closing tags.
  4. React reads the element and puts a real <h1> on the page.

Step 2 is real output. TypeScript is JavaScript with extra checks on what kind of value each thing holds. Many React projects use it, and every example in this series is written in it. TypeScript can also turn JSX into function calls.

We gave TypeScript 7.0.2 the line const element = <h1 className="greeting">Hello!</h1>. We used the same JSX setting that a new Vite project uses. This is what came out:

import { jsx as _jsx } from "react/jsx-runtime";
const element = _jsx("h1", { className: "greeting", children: "Hello!" });

You never write _jsx yourself. The tool writes it. But knowing it is there explains most of the JSX rules below.

While you develop, the tool writes a slightly different call, _jsxDEV. It lets React run extra checks. Then React’s warnings can point to the place in your code where the problem is. The playground uses _jsxDEV too. In the finished app, it’s _jsx.

Older tools turned JSX into a different call, React.createElement("h1", ...). You will still see it in old code and in many lessons online. It makes the same kind of element. We checked: in React 19.3, both calls make objects with the same type and props.

An everyday example

Think of a restaurant. You don’t go into the kitchen and cook. You write an order on a note: “one big heading that says Hello”. You give the note to the cook. The cook reads it and makes the food.

JSX is how you write the order. The element is the order note. React is the cook, and the page is the plate.

The exact version

An element is only a description. Making one doesn’t change the page. React compares the elements your code returns now with the elements it got last time. Then it changes only the parts of the page that are different. We will look at this closely in later parts.

You must never change an element after it is made. To stop you, React freezes elements while you develop. We tried to change a prop on an element in React 19.3, and it threw an error: Cannot assign to read only property 'className'.

Curly braces: JavaScript inside JSX

Inside JSX, curly braces { } mean “this part is JavaScript”. When your function runs, that JavaScript runs too, and its answer goes into the element.

You can put any JavaScript expression inside them. An expression is any piece of code that gives back a value. name is an expression. So are apples + 2, name.toUpperCase() and isOpen ? 'open' : 'closed'.

That last one is JavaScript’s ? : operator. It means: if isOpen is true, the answer is 'open'. If not, the answer is 'closed'.

export default function App() {
  const name = 'Ana'
  const isOpen = true

  return (
    <ul>
      <li>Name in capitals: {name.toUpperCase()}</li>
      <li>Letters in the name: {name.length}</li>
      <li>The shop is {isOpen ? 'open' : 'closed'}.</li>
    </ul>
  )
}

Run it. Then change isOpen to false and run it again.

A statement is not an expression. if, for and const are statements, so they can’t go inside { }. Later parts show how to choose what to show and how to show lists, without using them inside JSX.

Curly braces in attributes

You can give an attribute its value in two ways.

  • With quotes, the value is fixed text: href="https://react.dev".
  • With curly braces, the value comes from JavaScript: href={site}.

Don’t use both at once. This is a common mistake. The two boxes below show their value attribute as text, so you can see the difference:

export default function App() {
  const site = 'https://react.dev'

  return (
    <div>
      <input value={site} readOnly />
      <input value="{site}" readOnly />
    </div>
  )
}

Run it. The first box shows https://react.dev. The second box shows {site}, because inside quotes, {site} is just text.

readOnly means the reader can’t type in the box. Without it, React warns that the box has a value but no way to change it. Part 28 explains why.

What each value shows

Most values show up the way you would expect. A few show nothing at all. We gave React 19.3 each value inside a <p>, and asked it for the HTML it would make. This is what came out.

Inside { } What the page shows
'hello' hello
42 42
0 0
NaN NaN
'' (empty text) nothing
true or false nothing
null or undefined nothing
['a', 'b'] (an array) ab
{ a: 1 } (an object) an error

true, false, null and undefined show nothing. That is useful later, when we show something only sometimes. But notice 0. It is not on the “nothing” list. It shows on the page as 0. This causes a common mistake, and we’ll meet it in Part 7, on conditional rendering.

An array shows each of its items, one after another. An array of tags works too, but React then asks for a key on each tag. Part 8, on lists and keys, explains why.

An object is an error, because React does not know how to show it. You’ll see that error under Common mistakes.

The three rules of JSX

JSX is stricter than HTML. Browsers forgive missing tags in HTML. JSX does not. There are three rules.

Rule 1: return one parent tag

This function tries to return two tags side by side:

export default function App() {
  return (
    <h1>Shopping list</h1>
    <p>Three things to buy.</p>
  )
}

TypeScript stops here with the error “JSX expressions must have one parent element”. The playground doesn’t use TypeScript’s checker, so if you press Run there, the message is different: Unexpected token, expected ",". Both say the code can’t be read as it is.

The reason is in the picture above. Each tag becomes one function call. A function can return only one value. So two tags next to each other would be two values, and that doesn’t work.

The fix is to wrap them in one parent. You can use a <div>. But if you don’t want an extra <div> on the page, use an empty tag, <> and “. This is called a Fragment. It groups tags without adding anything to the page.

export default function App() {
  return (
    <>
      <h1>Shopping list</h1>
      <p>Three things to buy.</p>
    </>
  )
}

Run it. The heading and the paragraph appear with no <div> around them.

Rule 2: close every tag

In HTML, you can write <br> or <img> and never close them. In JSX, every tag must be closed. A tag with nothing inside closes itself with />:

export default function App() {
  return (
    <p>
      First line
      <br />
      Second line
    </p>
  )
}

Press Edit, remove the / from <br /> and press Run. The playground stops with an Unexpected token error.

Rule 3: most attribute names use camelCase

camelCase means writing words together, with a capital letter at the start of each new word: backgroundColor, onClick, tabIndex.

JSX attributes become keys of a JavaScript object. You saw this in step 2: className became a key in { className: "greeting", ... }. Later, your own code will read these values into variables. A variable’s name can’t have a dash in it. It also can’t be a word that JavaScript keeps for itself, like class.

So React uses the names that the browser’s own JavaScript uses for these attributes. That is why class becomes className, and for on a label becomes htmlFor. Other names with a dash become camelCase too: stroke-width becomes strokeWidth. Many names without a dash get a capital letter in the middle too: tabindex becomes tabIndex, and readonly becomes readOnly.

There is one exception, kept from React’s early days. Attributes that start with aria- or data- keep their dashes, the same as in HTML.

The style attribute is different again. It takes a JavaScript object, not a string:

export default function App() {
  return (
    <div>
      <p className="note">This paragraph has a class.</p>
      <p style={{ color: 'white', backgroundColor: 'teal', padding: 8 }}>
        This one has a style.
      </p>
    </div>
  )
}

The double curly braces {{ }} are not a new kind of code. The outer pair means “JavaScript here”. The inner pair is a normal JavaScript object. The style names inside it are camelCase too: backgroundColor, not background-color.

The number 8 became 8px. React adds px to most plain numbers, like padding, width and fontSize. A short list of styles take a plain number with no unit, like opacity, lineHeight, zIndex and flexGrow. React leaves those numbers alone. We checked both groups with React 19.3.

A capital letter means “my component”

Look at how the tool turns these two tags into calls. This is from the same TypeScript run:

const a = _jsx("greeting", {});
const b = _jsx(Greeting, {});

A tag that starts with a small letter, like <greeting />, becomes a string: "greeting". React treats it as an HTML tag.

A tag that starts with a capital letter, like <Greeting />, becomes the name Greeting with no quotes. That is a variable, so it must be something your code made or brought in with import, like a function. Functions like this are called components. The next part is all about them.

Comments in JSX

To leave a note for yourself inside JSX, put a JavaScript comment inside curly braces:

export default function App() {
  return (
    <div>
      {/* This note does not show on the page. */}
      <p>Only this shows.</p>
    </div>
  )
}

Common mistakes

Writing class instead of className

export default function App() {
  return <p class="note">Hello</p>
}

TypeScript stops you here. It says “Property ‘class’ does not exist” and asks “Did you mean ‘className’?”.

The playground doesn’t check types, so press Run. The text shows. But under it, in the Console area, React prints this warning:

Invalid DOM property `class`. Did you mean `className`?

The fix: <p className="note">.

Two tags with no parent

You saw this under rule 1. The fix is a <div> or a Fragment, <>....

Quotes around curly braces

href="{site}" is the text {site}, not the value of site. Use href={site} with no quotes.

Putting an object on the page

export default function App() {
  const user = { name: 'Ana', age: 12 }
  return <p>{user}</p>
}

TypeScript catches this one. Its error says the object can’t be used as a ReactNode. ReactNode is the name React’s types use for anything React can show.

The playground doesn’t check types, so press Run anyway. React stops with an error that starts with “Objects are not valid as a React child”. React can show text, numbers and arrays, but not a whole object. Show one of its fields instead: {user.name}.

Forgetting / in a tag with nothing inside

<br>, <img src={url}> and <input> must be <br />, <img src={url} /> and <input />.

Practice

Press Edit on any example above and try these.

  1. In the first example, add a line that says how many apples you have after eating one.
  2. Make a <p> with white text on a dark blue background, using style.
  3. Return two paragraphs with no parent. Read the error. Then fix it with a Fragment.
  4. Put {0}, {false} and {''} inside three paragraphs. Which ones show something? Does it match the table?
Answers
  1. For example: <p>After eating one, you have {apples - 1} apples.</p>. It shows “After eating one, you have 2 apples.”
  2. For example: <p style={{ color: 'white', backgroundColor: 'darkblue', padding: 8 }}>Hello</p>.
  3. The playground stops with an Unexpected token error. Put <> before the first tag and “ after the last one, and it works.
  4. Only {0} shows something: the number 0. {false} and {''} show nothing. That matches the table.

Interview questions

Try to answer each one out loud before you open the answer.

What is JSX? Can a browser run it?

JSX is a way to write HTML-like tags inside JavaScript. A browser can’t run it. A build tool turns each tag into a JavaScript function call first. A good answer adds that React doesn’t require JSX: you can call React’s functions yourself. But almost every React project uses JSX, because it is easier to read.

A common follow-up: “What does JSX turn into?”

What does JSX turn into?

A function call that makes a React element. With the JSX setup that new projects use, <h1 className="x">Hi</h1> becomes _jsx("h1", { className: "x", children: "Hi" }), imported from react/jsx-runtime. Older setups turned it into React.createElement("h1", { className: "x" }, "Hi"). Both calls return a plain object with a type and props. That object is the element.

A strong answer names the three calls in the newer setup. jsx is for no children or one child. jsxs is for several children written in the tag. jsxDEV is used while you develop. It also says this is why you no longer need import React from 'react' at the top of every file.

Why must a component return only one parent tag?

Each tag becomes one function call, and each call returns one object. A function can return only one value. Two tags side by side would be two values. Wrapping them in a parent, or in a Fragment (<>...), makes them one value again.

A strong answer adds two things. You can also return an array of tags, but then each one needs a key. Sometimes a Fragment itself needs a key, in a list. Then you write the long form, <Fragment key={id}>. The short <> can’t take a key.

Why does React use className instead of class?

JSX attributes become props, and your code reads props into variables. class already has its own meaning in JavaScript, so it can’t be a variable name. React uses the name of the matching browser property, className. The same reason gives htmlFor instead of for. React will still put class on the page, and print a warning. TypeScript refuses it.

What is the difference between title="name" and title={name}?

With quotes, the value is the fixed text name. With curly braces, the value is whatever the JavaScript variable name holds. Putting curly braces inside quotes, title="{name}", gives the text {name}. That is a common bug.

What do {0} and {false} show on the page?

{false} shows nothing, and so do true, null, undefined and an empty string. {0} shows the number 0.

This matters with the && operator. {count && <List />} means “show the List component if count is not zero or empty”. When count is zero, the answer of count && <List /> is 0 itself, so the page shows an extra 0. A strong answer names that bug and its fixes: {count > 0 && <List />}, or {count ? <List /> : null}.

Why must a component’s name start with a capital letter?

The JSX tool looks at the first letter. A small letter means a built-in HTML tag, so <button /> becomes the string "button". A capital letter means a variable, so <Button /> becomes your Button function. If you name your component button, React will look for an HTML tag called button and never call your function.

Sources

  • Writing Markup with JSX, react.dev: the three rules, Fragments and camelCase.
  • JavaScript in JSX with Curly Braces, react.dev: where curly braces can go, and the style object.
  • createElement, react.dev: what an element holds, why it must not be changed, and small vs capital letters.
  • Introducing the New JSX Transform, React blog, 2020: why JSX became jsx() calls from react/jsx-runtime.
  • The JSX output, the element objects and the value table above come from running TypeScript 7.0.2 and React 19.3.0 for this post.
  • This part follows the JSX Basics kata in react-katas. A kata is a small practice exercise. There you can try the same ideas in a full editor.

How useful was this post?

Click on a heart to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.