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.
- 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, writeclassNameandhtmlFor, and passstyleas an object. - JSX escapes values automatically, which protects your app from many script injection (XSS) attacks.
Full React roadmap (30 parts)
Beginner
- What Is React and How to Build Your First React App
- JSX Explained: Writing HTML-Like Code in JavaScript You are here
- Components and Props: Building Reusable UI Coming soon
- State with useState: Making Components Interactive Coming soon
- Handling Events and Forms in React Coming soon
- Conditional Rendering and Lists with Keys Coming soon
- Styling React Apps: Plain CSS, CSS Modules and Tailwind Coming soon
- useEffect: Side Effects, Data Fetching and Cleanup Coming soon
- Lifting State Up: Sharing Data Between Components Coming soon
- Mini Project: Build a To-Do App with React Coming soon
Intermediate
- useRef: Working with the DOM and Keeping Values Coming soon
- useContext: Sharing Data Without Prop Drilling Coming soon
- useReducer: Managing Complex State Coming soon
- Custom Hooks: Reusing Logic Across Components Coming soon
- React Router: Pages, Links and Dynamic Routes Coming soon
- Fetching Server Data with TanStack Query Coming soon
- Forms the Modern Way: Actions, useActionState and Validation Coming soon
- React with TypeScript: Typing Props, State and Events Coming soon
- Global State: Context vs Zustand vs Redux Toolkit Coming soon
- Testing React Apps with Vitest and React Testing Library Coming soon
Advanced
- How Rendering Works: Re-renders, Reconciliation and Keys Coming soon
- Performance: memo, useMemo, useCallback and the React Compiler Coming soon
- Code Splitting with lazy() and Suspense Coming soon
- Concurrent React: useTransition and useDeferredValue Coming soon
- Error Boundaries and Handling Errors Gracefully Coming soon
- The use() API and Optimistic UI with useOptimistic Coming soon
- React Server Components and Server Functions Coming soon
- Accessibility in React Apps Coming soon
- React Patterns for Large Apps: Folder Structure and Composition Coming soon
- 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.
{ } you can use variables, functions and logic. No separate template language to learn.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.
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.
const name = "Asha";
const element = <h1 className="title">Hello, {name}!</h1>;
console.log(element);
$ 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: {}
}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.
# 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.
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.
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 HTML | In JSX | Why |
|---|---|---|
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 |
disabled | disabled 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.
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 />));
$ 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>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" />));
$ 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>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.
export default function Broken() {
return (
<h1>Hello</h1>
<p>Welcome to DevDojo</p>
);
}
$ 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 |import { renderToStaticMarkup } from "react-dom/server";
function Fixed() {
return (
<>
<h1>Hello</h1>
<p>Welcome to DevDojo</p>
</>
);
}
console.log(renderToStaticMarkup(<Fixed />));
$ npx esbuild src/Fixed.jsx --jsx=automatic --bundle --platform=node --outfile=fx.js && node fx.js
<h1>Hello</h1><p>Welcome to DevDojo</p>Run Broken.jsx to see the error, then switch to Fixed.jsx: a fragment solves it without adding an extra tag.
| Error or symptom | Cause | Fix |
|---|---|---|
| Adjacent JSX elements must be wrapped in an enclosing tag | Returning two elements side by side | Wrap them in <>...</> or a <div> |
| Expected corresponding JSX closing tag for <br> | An empty tag that is not closed | Self-close it: <br />, <img /> |
Invalid DOM property class. Did you mean className? | HTML attribute names copied into JSX | Use className, htmlFor, camelCase events |
| Objects are not valid as a React child | Showing 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 string | style="color: red" | style={{ color: "red" }} |
| A stray 0 appears on the page | {items.length && <List />} when the length is 0 | Use a real boolean: {items.length > 0 && <List />} |
The page shows {name} as text | Curly braces inside quotes: title="{name}" | Remove the quotes: title={name} |
Unexpected token when using if in JSX | Statements 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 returnundefined. - 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
.jsxextension for files that contain JSX (or.tsxwith TypeScript). Vite expects it. - Trust automatic escaping. Text you put in
{ }is escaped, so a user typing<script>shows as text. Only usedangerouslySetInnerHTMLwith 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>
- Fix every attribute and close every empty tag so it compiles.
- Store the product in an object:
{ name, price, inStock }, and show the name and price with curly braces (format the price as₹usingtoLocaleString("en-IN")). - Show “In stock” in green or “Out of stock” in red using a ternary inside the
styleobject. - Add an array of three features and render them as a list with
map()and akey. - 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.
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.