React Part 2: JSX Explained: Writing HTML-Like Code in JavaScript

React Part 2 banner: JSX code turning into a jsx() call and then a heading on the page
PART 2 React Series · DevDojo JSX Explained HTML-Like Code in JavaScript Curly braces, className, fragments and the rules beginners miss <h1>Hi, {name}</h1>You write JSX jsx(“h1”, {…})Build tool turns it into JS Hi, AshaReact shows it on the page devdojo.co.in · Beginner friendly · React 19 + Vite 8
Series: React Part 2 of 30

What you should know first: read React Part 1: What Is React and How to Build Your First React App. You should have Node.js installed and know how to start a Vite project with npm run dev.

When you open any React component, the first thing you notice is code that looks like HTML sitting right inside JavaScript. That is JSX in React, and it is the syntax you will write every single day as a React developer. In this second part of the React series you will learn what JSX really is, how a build tool turns it into plain JavaScript, how to put values inside it with curly braces, and the small rules (like className and closing every tag) that confuse almost every beginner. Every example below is real code that we ran, with the real output.

Quick Summary
  • JSX is a syntax extension for JavaScript that lets you describe UI with HTML-like tags. Browsers cannot read it directly.
  • Your build tool (Vite, Next.js) changes every JSX tag into a function call such as jsx("h1", {...}), which creates a plain JavaScript object called a React element.
  • Use { } to put any JavaScript expression inside JSX: variables, maths, function calls, ternaries, map().
  • Main rules: return one parent element (or a fragment <>...</>), close every tag, use camelCase attributes, write className and htmlFor, and pass style as an object.
  • JSX escapes values automatically, which protects your app from many script injection (XSS) attacks.
Full React roadmap (30 parts)

Beginner

  1. What Is React and How to Build Your First React App
  2. JSX Explained: Writing HTML-Like Code in JavaScript You are here
  3. Components and Props: Building Reusable UI Coming soon
  4. State with useState: Making Components Interactive Coming soon
  5. Handling Events and Forms in React Coming soon
  6. Conditional Rendering and Lists with Keys Coming soon
  7. Styling React Apps: Plain CSS, CSS Modules and Tailwind Coming soon
  8. useEffect: Side Effects, Data Fetching and Cleanup Coming soon
  9. Lifting State Up: Sharing Data Between Components Coming soon
  10. Mini Project: Build a To-Do App with React Coming soon

Intermediate

  1. useRef: Working with the DOM and Keeping Values Coming soon
  2. useContext: Sharing Data Without Prop Drilling Coming soon
  3. useReducer: Managing Complex State Coming soon
  4. Custom Hooks: Reusing Logic Across Components Coming soon
  5. React Router: Pages, Links and Dynamic Routes Coming soon
  6. Fetching Server Data with TanStack Query Coming soon
  7. Forms the Modern Way: Actions, useActionState and Validation Coming soon
  8. React with TypeScript: Typing Props, State and Events Coming soon
  9. Global State: Context vs Zustand vs Redux Toolkit Coming soon
  10. Testing React Apps with Vitest and React Testing Library Coming soon

Advanced

  1. How Rendering Works: Re-renders, Reconciliation and Keys Coming soon
  2. Performance: memo, useMemo, useCallback and the React Compiler Coming soon
  3. Code Splitting with lazy() and Suspense Coming soon
  4. Concurrent React: useTransition and useDeferredValue Coming soon
  5. Error Boundaries and Handling Errors Gracefully Coming soon
  6. The use() API and Optimistic UI with useOptimistic Coming soon
  7. React Server Components and Server Functions Coming soon
  8. Accessibility in React Apps Coming soon
  9. React Patterns for Large Apps: Folder Structure and Composition Coming soon
  10. Final Project: Build and Deploy a Complete React App Coming soon

What is JSX in React?

JSX stands for JavaScript XML. It is a syntax extension for JavaScript that lets you write markup that looks like HTML inside your JavaScript files. React made it popular, and today it is the standard way to describe what a React component should show on the screen.

Here is the smallest possible piece of JSX:

const heading = <h1>Hello, DevDojo!</h1>;

This is not a string (there are no quotes) and it is not real HTML either. It is a JavaScript expression. You can store it in a variable, return it from a function, put it inside an array or pass it to another component, exactly like a number or an object.

Looks like HTMLTags, attributes and nesting feel familiar, so you can read a component and picture the page.
Works like JavaScriptInside { } you can use variables, functions and logic. No separate template language to learn.
Keeps UI and logic togetherThe markup and the code that controls it live in the same component file, which makes it easier to change.
Is JSX required? No. React works without JSX, but writing UI with raw function calls is long and hard to read. Almost every React project, tutorial and job uses JSX, so learning it well is worth it.

How JSX becomes JavaScript

Browsers only understand plain JavaScript. So before your code reaches the browser, a build tool (Vite uses a fast compiler for this, Next.js uses SWC, older setups use Babel) transforms every JSX tag into a normal function call. With React 19 the modern “automatic” JSX runtime is used, so the call is jsx() or jsxs() imported from react/jsx-runtime. You never import it yourself; the tool adds it for you.

1. JSX you write <h1 className= “title”>Hi</h1> 2. Build tool jsx(“h1”, { className, children}) 3. React element { type: “h1”, props: {…} } 4. Real DOM Hi <h1 class=”title”> Steps 1-2 happen when you build. Steps 3-4 happen in the browser when React runs.
From JSX to the screen: JSX is compiled to jsx() calls, which create React elements that React DOM turns into real HTML.

Let us prove it. We wrote one line of JSX, asked esbuild (a JSX compiler) to transform it, and then ran it with Node.js to print the React element it creates. Click ▶ Run to see the real output.

hello.jsx — jsx-playground — Visual Studio Code
jsx-playground › src › hello.jsx
const name = "Asha";
const element = <h1 className="title">Hello, {name}!</h1>;
console.log(element);
TERMINALOUTPUTPROBLEMSReal output (esbuild 0.28, React 19.3, Node.js 22)
$ npx esbuild src/hello.jsx --jsx=automatic
import { jsxs } from "react/jsx-runtime";
const name = "Asha";
const element = /* @__PURE__ */ jsxs("h1", { className: "title", children: [
  "Hello, ",
  name,
  "!"
] });
console.log(element);

$ npx esbuild src/hello.jsx --jsx=automatic --bundle --platform=node --outfile=out.js && node out.js
{
  '$$typeof': Symbol(react.transitional.element),
  type: 'h1',
  key: null,
  props: { className: 'title', children: [ 'Hello, ', 'Asha', '!' ] },
  _owner: null,
  _store: {}
}
⎇ mainLn 3, Col 1Spaces: 2UTF-8LFJavaScript JSX

Click ▶ Run to see the compiled JavaScript and the React element object it creates.

Look at the output carefully. The tag name became type: 'h1', the attribute became className inside props, and the text plus the {name} value became the children array. A React element is just a small, plain object that describes what you want. React reads these objects and updates the real page for you, as you saw in Part 1.

Set up a JSX playground

You can try every example in this post inside a fresh Vite + React project. If you followed Part 1 you can reuse that project instead.

jsx-playground — bash
# 1. Create a new React project with Vite (Node.js 20.19+ or 22.12+)
$ npm create vite@latest jsx-playground -- --template react
# 2. Install the packages and start the dev server
$ cd jsx-playground
$ npm install
$ npm run dev
# 3. Open http://localhost:5173/ in your browser

Open src/App.jsx, delete the starter code, and paste the examples below. Save the file and the browser at http://localhost:5173/ updates instantly.

Curly braces: JavaScript inside JSX

Curly braces { } are the doorway from JSX back into JavaScript. Anything inside them is evaluated and the result is shown. The one rule: you can put an expression (something that produces a value) inside, but not a statement such as if, for or const.

Works inside { } (expressions) {name}a variable {price * 2}maths {formatDate(d)}a function call {ok ? “Yes” : “No”}a ternary {isNew && <Badge/>}logical AND {items.map(…)}a list Does not work (statements) {if (ok) {…}}use a ternary {for (…) {…}}use map() {const x = 5}declare above return {user}objects can’t be text Tip: do the logic above the return, then show the result with { }.
Curly braces accept expressions that produce a value. Statements like if and for must go outside the JSX or be replaced by a ternary and map().

You can use curly braces in two places: as text content between tags (<p>{count}</p>) and as an attribute value (<img src={user.avatarUrl} />). When the attribute is a fixed string, use quotes instead: alt="Profile photo". Never wrap curly braces in quotes: src="{url}" gives you the literal text {url}.

Some values are special when you show them as text: true, false, null and undefined render nothing at all. That is why {isLoggedIn && <p>Welcome</p>} works for “show this only if”. But the number 0 is rendered, which leads to a famous bug we cover in the errors table.

Double curly braces? In style={{ color: "red" }} the outer braces mean “JavaScript starts here” and the inner braces are a normal JavaScript object. It is not a special syntax.

The rules of JSX

JSX is stricter than HTML. A browser forgives a missing closing tag; a JSX compiler does not. Click through the four tabs below to learn the rules that cover almost every beginner mistake.

Return a single root element. A component can return only one thing, because JSX becomes one function call and a function returns one value. If you need several elements side by side, wrap them in a <div> or, better, a fragment: <> ... </>. A fragment groups elements without adding an extra tag to the HTML. Inside a list you use the long form <Fragment key={id}> when you need a key.

Close every tag. Tags with content need a closing tag (<li>...</li>). Tags without content must be self-closed with />: write <img />, <input /> and <br />, never just <br>. Your own components can self-close too: <Greeting name="Asha" />.

Use camelCase for attributes. JSX attributes become JavaScript object keys, so they follow JavaScript naming: onClick, tabIndex, maxLength, strokeWidth. Two words are reserved in JavaScript, so they change name: class becomes className and for becomes htmlFor. The exceptions are aria-* and data-* attributes, which keep their dashes.

Style is an object, comments use braces. Inline styles take a JavaScript object with camelCase keys: style={{ backgroundColor: "navy", fontSize: 18 }}. A plain number like 18 means pixels. HTML comments (<!-- -->) do not work in JSX; write {/* comment */} instead.

HTML vs JSX attributes

Keep this table close while you convert HTML snippets (for example from a template or a design) into JSX.

In HTMLIn JSXWhy
class="card"className="card"class is a JavaScript keyword
for="email"htmlFor="email"for is a JavaScript keyword
onclick="go()"onClick={go}camelCase, and you pass a function, not a string
tabindex="0"tabIndex={0}camelCase attribute names
style="color: red"style={{ color: "red" }}style takes an object
<br>, <img src="a.png"><br />, <img src="a.png" />every tag must be closed
<!-- note -->{/* note */}comments are JavaScript comments
disableddisabled or disabled={true}an attribute with no value means true

The official React guide Writing Markup with JSX also links to an online HTML-to-JSX converter, which is handy for big snippets.

A complete JSX example

Now let us combine everything: a fragment, a JSX comment, className, a style object, htmlFor, a self-closed <input />, expressions, a ternary, map() with keys and &&. To show you the exact HTML React produces, we render each component to a string with renderToStaticMarkup from react-dom/server and print it. In your Vite app you would just use <ProfileCard /> inside App.jsx.

2 files — jsx-playground — Visual Studio Code
jsx-playground › src › ProfileCard.jsx
import { renderToStaticMarkup } from "react-dom/server";

const user = {
  name: "Rahul Sharma",
  role: "Frontend Developer",
  skills: ["React", "JSX", "CSS"],
  isOnline: true,
};

function ProfileCard() {
  const cardStyle = { padding: 16, borderRadius: 12, border: "1px solid #ddd" };

  return (
    <>
      {/* A JSX comment: it does not appear in the HTML */}
      <section className="card" style={cardStyle}>
        <h2>{user.name.toUpperCase()}</h2>
        <p>{user.role} · {user.skills.length} skills</p>
        <label htmlFor="msg">Message</label>
        <input id="msg" type="text" placeholder="Say hi" />
        <span>{user.isOnline ? "Online" : "Offline"}</span>
      </section>
    </>
  );
}

console.log(renderToStaticMarkup(<ProfileCard />));
TERMINALOUTPUTPROBLEMSReal output (React 19.3, Node.js 22)
$ npx esbuild src/ProfileCard.jsx --jsx=automatic --bundle --platform=node --outfile=pc.js && node pc.js
<section class="card" style="padding:16px;border-radius:12px;border:1px solid #ddd"><h2>RAHUL SHARMA</h2><p>Frontend Developer · 3 skills</p><label for="msg">Message</label><input id="msg" type="text" placeholder="Say hi"/><span>Online</span></section>
⎇ mainLn 27, Col 1Spaces: 2UTF-8LFJavaScript JSX
jsx-playground › src › SkillList.jsx
import { renderToStaticMarkup } from "react-dom/server";

const skills = ["HTML", "CSS", "JavaScript", "React"];

function SkillList({ title }) {
  return (
    <div>
      <h3>{title}</h3>
      <ul>
        {skills.map((skill, index) => (
          <li key={skill}>
            {index + 1}. {skill}
          </li>
        ))}
      </ul>
      {skills.length > 3 && <p>Great progress!</p>}
    </div>
  );
}

console.log(renderToStaticMarkup(<SkillList title="My skills" />));
TERMINALOUTPUTPROBLEMSReal output (React 19.3, Node.js 22)
$ npx esbuild src/SkillList.jsx --jsx=automatic --bundle --platform=node --outfile=sl.js && node sl.js
<div><h3>My skills</h3><ul><li>1. HTML</li><li>2. CSS</li><li>3. JavaScript</li><li>4. React</li></ul><p>Great progress!</p></div>
⎇ mainLn 21, Col 1Spaces: 2UTF-8LFJavaScript JSX

Click the two tabs, then ▶ Run each one to see the exact HTML that React creates.

Notice in the output how className became class, htmlFor became for, the style object became a CSS string with 16px, the comment and the fragment disappeared completely, and the input was written as <input .../>. In the second tab, map() turned an array of strings into four <li> elements. Each item has a key so React can track it; you will learn lists and keys in depth in Part 6.

Common errors and fixes

Here is the error you will see most often as a beginner. We compiled the broken file with Babel (the same message appears in many editors and tools). Click both tabs and ▶ Run.

2 files — jsx-playground — Visual Studio Code
jsx-playground › src › Broken.jsx
export default function Broken() {
  return (
    <h1>Hello</h1>
    <p>Welcome to DevDojo</p>
  );
}
TERMINALOUTPUTPROBLEMSReal output (Babel 8)
$ npx babel src/Broken.jsx --presets @babel/preset-react
SyntaxError: src/Broken.jsx: Adjacent JSX elements must be wrapped in an enclosing tag. Did you want a JSX fragment <>...</>? (4:4)

  2 |   return (
  3 |     <h1>Hello</h1>
> 4 |     <p>Welcome to DevDojo</p>
    |     ^
  5 |   );
  6 | }
  7 |
⎇ mainLn 6, Col 1Spaces: 2UTF-8LFJavaScript JSX
jsx-playground › src › Fixed.jsx
import { renderToStaticMarkup } from "react-dom/server";

function Fixed() {
  return (
    <>
      <h1>Hello</h1>
      <p>Welcome to DevDojo</p>
    </>
  );
}

console.log(renderToStaticMarkup(<Fixed />));
TERMINALOUTPUTPROBLEMSReal output (React 19.3, Node.js 22)
$ npx esbuild src/Fixed.jsx --jsx=automatic --bundle --platform=node --outfile=fx.js && node fx.js
<h1>Hello</h1><p>Welcome to DevDojo</p>
⎇ mainLn 12, Col 1Spaces: 2UTF-8LFJavaScript JSX

Run Broken.jsx to see the error, then switch to Fixed.jsx: a fragment solves it without adding an extra tag.

Error or symptomCauseFix
Adjacent JSX elements must be wrapped in an enclosing tagReturning two elements side by sideWrap them in <>...</> or a <div>
Expected corresponding JSX closing tag for <br>An empty tag that is not closedSelf-close it: <br />, <img />
Invalid DOM property class. Did you mean className?HTML attribute names copied into JSXUse className, htmlFor, camelCase events
Objects are not valid as a React childShowing a whole object like {user}Show a field: {user.name}, or use JSON.stringify while debugging
The style prop expects a mapping from style properties to values, not a stringstyle="color: red"style={{ color: "red" }}
A stray 0 appears on the page{items.length && <List />} when the length is 0Use a real boolean: {items.length > 0 && <List />}
The page shows {name} as textCurly braces inside quotes: title="{name}"Remove the quotes: title={name}
Unexpected token when using if in JSXStatements inside { }Use a ternary, or compute the value above return

Best practices for writing JSX

  • Wrap multi-line JSX in parentheses after return. Without them, JavaScript’s automatic semicolon insertion can make the function return undefined.
  • Keep logic out of the markup. Calculate values (filtered lists, formatted prices) in variables above return, then just show them with { }.
  • Prefer fragments over extra divs when you only need grouping. Fewer wrapper divs means cleaner HTML and easier CSS.
  • Name components with a capital letter. <profileCard /> is treated as an HTML tag; <ProfileCard /> is your component.
  • Use the .jsx extension for files that contain JSX (or .tsx with TypeScript). Vite expects it.
  • Trust automatic escaping. Text you put in { } is escaped, so a user typing <script> shows as text. Only use dangerouslySetInnerHTML with HTML you have cleaned and trust.
  • Let a formatter handle layout. Install Prettier in VS Code so your JSX indentation is always consistent.

Want to go deeper on the rules for curly braces? Read the React docs page JavaScript in JSX with Curly Braces.

Practice task

Convert this HTML into a React component called ProductCard inside your Vite project:

<div class="product"><img src="phone.png"><h3>Phone X</h3><p style="color: green">In stock</p><label for="qty">Qty</label><input id="qty" type="number"></div>

  1. Fix every attribute and close every empty tag so it compiles.
  2. Store the product in an object: { name, price, inStock }, and show the name and price with curly braces (format the price as ₹ using toLocaleString("en-IN")).
  3. Show “In stock” in green or “Out of stock” in red using a ternary inside the style object.
  4. Add an array of three features and render them as a list with map() and a key.
  5. Bonus: add a {/* comment */} and check in DevTools that it does not appear in the HTML.

FAQ

Is JSX the same as HTML?

No. JSX only looks like HTML. It is compiled into JavaScript function calls, it is stricter (every tag must close) and it uses JavaScript names such as className and onClick.

Can I use JSX without React?

Yes. JSX is just syntax, and other libraries such as Preact and Solid also use it with their own runtime. But it was created for React and is mostly used with it.

Do I need to import React to use JSX?

Not any more. Since React 17 the automatic JSX runtime adds the needed import for you, so import React from "react" is no longer required just for JSX.

What is the difference between .js and .jsx files?

Both are JavaScript. The .jsx extension tells your editor and build tool that the file contains JSX. Vite expects JSX in .jsx or .tsx files.

Why does React use className instead of class?

JSX attributes become keys of a JavaScript object, and class is a reserved word in JavaScript. React uses the DOM property name className instead.

Is JSX safe from XSS attacks?

Mostly yes. React escapes every value you put inside curly braces before it reaches the page. The risk returns only if you use dangerouslySetInnerHTML with untrusted HTML or put user input into URLs like href without checking it.

Conclusion

You now understand JSX in React from the inside: it is HTML-like syntax that a build tool turns into jsx() calls, which create plain React element objects. You learned how curly braces bring JavaScript expressions into your markup, the four rules that prevent most errors (one parent, close every tag, camelCase attributes, style objects and brace comments), how HTML attributes map to JSX, and how to read and fix the most common JSX error messages.

Missed the setup? Go back to React Part 1: What Is React and How to Build Your First React App. Curious about how React handles modern hooks? Our useEffect guide is a good preview of what is coming later in the series.

Next part – React Part 3: Components and Props: Building Reusable UI. We will split a page into small components, pass data into them with props, use the special children prop and learn how to think in components.

New posts every day on DevDojo

Follow the full React series – 30 parts from “what is React” to building and deploying a complete app. Bookmark devdojo.co.in and share your Practice task solution in the comments.

Share