Menu

What Is JSX? React JSX Syntax Rules With Examples

JSX is a syntax extension for JavaScript that lets you write HTML-like markup inside React components. Learn its rules: one root element, closed tags, className, camelCase attributes, curly braces, inline styles, comments, and what it compiles to.

This page includes runnable editors - edit, run, and see output instantly.

JSX is a syntax extension for JavaScript that lets you write HTML-like markup inside your code. React components return JSX to describe what should appear on screen, and a build tool compiles it into ordinary JavaScript function calls. It looks like HTML, but a few rules differ, and curly braces let you put any JavaScript value into the markup.

Everything inside {} is JavaScript: a property, a method call, even map turning an array into list items. Add 'design' to the skills array and both the count and the list update.

JSX rules

Return one root element

A component returns a single value, so its JSX needs a single parent. Two siblings at the top level are a syntax error. When you do not want an extra <div> in the page, wrap them in a fragment, <>...</>:

// Error: Adjacent JSX elements must be wrapped in an enclosing tag
return (
    <h1>Title</h1>
    <p>Text</p>
);

// OK: a fragment groups them without adding a DOM element
return (
    <>
        <h1>Title</h1>
        <p>Text</p>
    </>
);

When you need a key on the group, use the long form <Fragment key={id}>; see fragments.

Close every tag

HTML lets you leave some tags open. JSX does not. Elements without children close themselves with />: <img />, <br />, <input />, and your own components such as <Avatar />.

Use className, htmlFor and camelCase attributes

JSX attributes become keys of a JavaScript object, so they follow JavaScript naming. class and for are reserved words in JavaScript, which is why JSX uses className and htmlFor. Multi-word attributes and every event handler are camelCase: tabIndex, maxLength, autoFocus, onClick, onChange. Two exceptions keep their dashes: aria-* and data-* attributes are written exactly as in HTML.

Change className="field" to class="field". The label keeps its style, because React sets the attribute anyway, but a development build logs "Invalid DOM property class. Did you mean className?" The preview runs like a production build, so you will not see that warning here; use className. A string attribute uses quotes (type="email"); any other value uses braces (maxLength={40}, disabled={false}).

Curly braces take expressions, not statements

Inside {} you can write anything that produces a value: a variable, arithmetic, a function call, a ternary, &&, a template literal. Statements such as if, for and const do not produce a value, so they cannot go there. Do the work before the return, or use an expression form:

Look closely before you click: the page shows a stray 0. count && <p>...</p> evaluates to 0 when the count is zero, and React renders numbers, even zero. Change it to count > 0 && <p>You have mail.</p> and the 0 goes away. false, null, undefined and true render nothing, which is why a boolean on the left of && is safe. The page on conditional rendering covers the patterns.

Inline styles use an object

The style attribute takes a JavaScript object, not a CSS string. That gives the "double braces" look: the outer pair means "JavaScript here", the inner pair is the object literal. Property names are camelCase (backgroundColor, fontSize), and a plain number means pixels for most size properties.

Because the style is an object, it can be computed from state like any other value. Try changing fontSize: big ? 24 : 16 to fontSize: big ? '2em' : '1em': strings are passed through with their unit.

Comments

A comment inside JSX goes in curly braces: {/* this is a comment */}. HTML comments (<!-- -->) are a syntax error. Outside the markup, in the function body, ordinary // comments work as usual.

return (
    <div>
        {/* The header is hidden on small screens */}
        <Header />
    </div>
);

Converting HTML to JSX

Pasting HTML into a component is a common first step, and most of it works. These are the changes you usually have to make:

  • class becomes className, and for becomes htmlFor.
  • Void tags close themselves: <br> becomes <br />, <img src="a.png"> becomes <img src="a.png" />.
  • style="color: red; font-size: 14px" becomes style={{ color: 'red', fontSize: 14 }}.
  • Event attributes take a function, not a string: onclick="save()" becomes onClick={save}.
  • Attribute names become camelCase: tabindex becomes tabIndex, and SVG's stroke-width becomes strokeWidth.
  • <!-- comments --> become {/* comments */}.
  • Several top-level elements need one parent or a fragment.

Online HTML to JSX converters do these steps for you, but knowing them makes the error messages easy to read when you miss one.

What JSX compiles to

Browsers cannot run JSX. A compiler (such as Babel, SWC or Oxc, which Vite and the frameworks run for you) turns each tag into a function call from react/jsx-runtime:

// What you write
const element = <h1 className="title">Hello, {name}</h1>;
const button = <Button size="small">OK</Button>;
// What the compiler produces (roughly)
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime';

const element = _jsxs('h1', { className: 'title', children: ['Hello, ', name] });
const button = _jsx(Button, { size: 'small', children: 'OK' });

A lowercase tag becomes a string ('h1', an HTML element), a capitalized one becomes a reference to your function (Button), attributes become the props object, and the content becomes children. Older setups compiled to React.createElement(type, props, ...children) instead, which is why old files start with import React from 'react' even when they never mention React. The call returns a plain object called a React element. You can log one:

The console shows that heading is just data: a type 'h3' and a props object. That is also why JSX can be stored in variables, passed to functions and returned from them.

JSX escapes text for you

Any string you put in braces is rendered as text, never as HTML. If a user types <img src=x onerror=alert(1)> into a comment box, React shows those characters on the screen instead of creating an image tag. This blocks the most common form of cross-site scripting (XSS) by default.

The first paragraph prints the tags as plain characters. The second uses dangerouslySetInnerHTML, which skips escaping and inserts the string as real HTML, so "our own" is bold. The long name is a warning: only pass HTML you produced yourself or cleaned with a sanitizer such as DOMPurify. Passing user input there reopens the XSS hole that JSX closed.

Frequently Asked Questions

What does JSX stand for?

JSX stands for JavaScript XML. It is a syntax extension that lets you write markup that looks like HTML inside JavaScript code.

Is JSX HTML?

No. JSX looks like HTML but compiles to JavaScript function calls that create React elements. That is why it uses className instead of class, camelCase attribute names, and requires every tag to be closed.

Can I use React without JSX?

Yes. JSX is optional: you can call createElement('h1', { className: 'title' }, 'Hello') from react directly. Almost every React codebase uses JSX because nested markup is much easier to read.

Why can't I use an if statement inside JSX?

Curly braces in JSX accept expressions, things that produce a value, and if is a statement. Use a ternary (cond ? a : b), &&, or compute the value in a variable before the return.

How do I write a comment in JSX?

Wrap a JavaScript comment in curly braces: {/* like this */}. HTML comments (<!-- -->) are a syntax error in JSX.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED