Lesson 2 of 12

React JSX

Every React component you saw in the React Introduction returned something that looked like HTML. That syntax is called React JSX, or simply JSX. It lets you write the structure of your interface right next to the JavaScript that controls it. JSX is easy to pick up because it looks familiar, but it has a few rules that are different from HTML. This lesson covers all of them.

What is JSX?

JSX is a syntax extension for JavaScript. Browsers cannot run it directly, so a compiler such as Babel turns each JSX tag into an ordinary function call before the code runs. With the classic setup used in our browser examples, a tag becomes a call to React.createElement(). That is the reason every example imports React, even when the name never appears in your code.

// What you write
const heading = <h1 className="title">Hello, Priya</h1>;

// Roughly what Babel turns it into
const heading = React.createElement(
  "h1",
  { className: "title" },
  "Hello, Priya"
);

The result is a plain JavaScript object that describes an element. Because JSX is just an expression, you can store it in a variable, return it from a function or pass it to another function. You could write React without JSX, but almost nobody does: the tag version is far easier to read.

Embedding JavaScript with curly braces

Inside JSX, curly braces { } open a window back into JavaScript. Anything that produces a value – a variable, a calculation, a function call, a ternary – can go inside. Statements such as if or for cannot, because they do not produce a value. You will learn the expression-friendly alternatives in Conditional Rendering and Lists and Keys.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Curly braces in JSX</title>
  <script src="https://unpkg.com/@babel/[email protected]/babel.min.js"></script>
  <style>
    body { font-family: system-ui, sans-serif; padding: 16px; line-height: 1.5; }
    .price { color: #0a7a3d; font-weight: bold; }
  </style>
</head>
<body>
  <div id="root"></div>

  <script type="text/babel" data-type="module">
import React from "https://esm.sh/[email protected]";
import { createRoot } from "https://esm.sh/[email protected]/client";

const product = { name: "Filter coffee powder", price: 240, weight: "500 g" };
const gstRate = 0.05;

function formatRupees(amount) {
  return "₹" + amount.toFixed(2);
}

function App() {
  const total = product.price * (1 + gstRate);

  return (
    <div>
      <h2>{product.name.toUpperCase()}</h2>
      <p>Pack size: {product.weight}</p>
      <p>Price before tax: {formatRupees(product.price)}</p>
      <p className="price">Price with 5% GST: {formatRupees(total)}</p>
      <p>{total > 250 ? "Free delivery on this item" : "Delivery charge applies"}</p>
    </div>
  );
}

createRoot(document.getElementById("root")).render(<App />);
  </script>
</body>
</html>

In React JSX, text inside braces is automatically escaped. If product.name contained <script>, React would show it as harmless text rather than run it. This protects your pages from a whole class of security problems called cross-site scripting.

The rules of React JSX

1. Return one root element

A component must return a single element. If you need two siblings, wrap them in a <div>, or – when you do not want an extra box in the HTML – in a fragment, written as empty tags <> … </>. Fragments group elements without adding anything to the DOM.

2. Close every tag

HTML is forgiving about tags such as <img>, <br> and <input>. JSX is not: every tag must be closed, either with a closing tag or with a self-closing slash, such as <img /> and <br />.

3. Use camelCase attribute names

JSX attributes become keys of a JavaScript object, so they follow JavaScript naming. class is a reserved word in JavaScript, so you write className. The for attribute of a label becomes htmlFor. Event attributes use camelCase too: onClick, onChange. Two exceptions keep their dashes: aria-* and data-* attributes.

HTMLJSX
class="card"className="card"
for="email"htmlFor="email"
onclick="…"onClick={handleClick}
tabindex="0"tabIndex={0}
style="font-size: 18px"style={{ fontSize: 18 }}
<br><br />
<!-- comment -->{/* comment */}

4. Styles are objects

The style attribute takes a JavaScript object, not a string. That is where the double braces come from: the outer pair says “JavaScript here”, and the inner pair is the object itself. Property names are camelCase (backgroundColor, not background-color), and a plain number for a size means pixels. For most styling, a className and a stylesheet are still the better choice; keep inline styles for values that change with data.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Fragments, className and style objects</title>
  <script src="https://unpkg.com/@babel/[email protected]/babel.min.js"></script>
  <style>
    body { font-family: system-ui, sans-serif; padding: 16px; line-height: 1.5; }
    .badge { display: inline-block; padding: 2px 10px; border-radius: 999px; background: #e6f4ea; color: #0a7a3d; }
    img { border-radius: 8px; display: block; margin-top: 8px; }
  </style>
</head>
<body>
  <div id="root"></div>

  <script type="text/babel" data-type="module">
import React from "https://esm.sh/[email protected]";
import { createRoot } from "https://esm.sh/[email protected]/client";

const stock = 7;

function App() {
  const barWidth = stock * 10;

  return (
    <>
      {/* A fragment: no extra div in the page */}
      <h2>Kanchipuram silk saree</h2>
      <span className="badge">In stock</span>
      <p style={{ fontSize: 14, color: "#555" }}>
        Only {stock} left
      </p>
      <div style={{ background: "#eee", width: 200, height: 10, borderRadius: 5 }}>
        <div style={{ background: "#d9480f", width: barWidth + "%", height: "100%", borderRadius: 5 }} />
      </div>
      <img
        src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='60'%3E%3Crect width='120' height='60' fill='%23f4c430'/%3E%3C/svg%3E"
        alt="Saffron colour swatch"
        width="120"
        height="60"
      />
    </>
  );
}

createRoot(document.getElementById("root")).render(<App />);
  </script>
</body>
</html>

Watch out: The most common React JSX error for beginners is “Adjacent JSX elements must be wrapped in an enclosing tag”. It means your component returns two elements side by side. Wrap them in a <div> or a fragment. The second most common one is writing class instead of className – it may appear to work, but React prints a warning in the console.

Wrapping JSX in brackets

When JSX spans several lines, wrap it in round brackets after return, as all the examples here do. Without them, JavaScript’s automatic semicolon insertion can end the statement right after the word return, and your component would return undefined. The brackets also make it obvious where the markup starts and ends.

Tip: Converting a large chunk of existing HTML to JSX by hand is tedious. Change it a section at a time: fix class, close the void tags, convert inline styles to objects, and run it after each step so errors stay small and easy to find.

Further reading

Key takeaways

  • JSX is HTML-like syntax that compiles to JavaScript function calls.
  • Use curly braces to put any JavaScript expression inside JSX.
  • Return one root element; use a fragment <>…</> to avoid an extra div.
  • Close every tag and use camelCase attributes: className, htmlFor, onClick.
  • The style attribute takes an object: style={{ fontSize: 18 }}.
  • React escapes values in braces, which protects against cross-site scripting.

Quick quiz

Check what you learned – pick an answer.

1. Which JSX is correct?

2. What goes inside { } in JSX?

3. How do you set a red text colour with an inline style in JSX?

Practice exercise

Build a ContactCard component that shows a name in an <h2>, a phone number and a city, using variables in curly braces. Give the outer element a className of "card" and make the city text grey with an inline style.

Show a hint

Declare const person = { … } above the component, then use {person.name} and so on.

Show the solution
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Contact card</title>
  <script src="https://unpkg.com/@babel/[email protected]/babel.min.js"></script>
  <style>
    body { font-family: system-ui, sans-serif; padding: 16px; line-height: 1.5; }
    .card { border: 1px solid #ccc; border-radius: 8px; padding: 12px; max-width: 280px; }
  </style>
</head>
<body>
  <div id="root"></div>

  <script type="text/babel" data-type="module">
import React from "https://esm.sh/[email protected]";
import { createRoot } from "https://esm.sh/[email protected]/client";

const person = { name: "Karthik Raman", phone: "98400 12345", city: "Chennai" };

function App() {
  return (
    <div className="card">
      <h2>{person.name}</h2>
      <p>Phone: {person.phone}</p>
      <p style={{ color: "#666" }}>{person.city}</p>
    </div>
  );
}

createRoot(document.getElementById("root")).render(<App />);
  </script>
</body>
</html>

Progress is saved in this browser only.