Skip to content
React fundamentals 2 min read

JSX

JSX is the syntax extension that lets you write markup directly inside JavaScript. It looks like HTML, but it’s really sugar over function calls that produce React elements. Understanding what JSX is—and the small ways it differs from HTML—makes the rest of React feel natural.

What JSX compiles to

A JSX tag is just a more readable way to write a React.createElement call. Your build tool (Vite, Next.js, etc.) compiles it away before it reaches the browser.

const el = <h1 className="title">Hello</h1>;
// compiles to:
const el = React.createElement("h1", { className: "title" }, "Hello");

Because JSX is an expression, it must return a single root element. Wrap siblings in a parent or a fragment.

Embedding expressions with { }

Curly braces drop you back into JavaScript. Anything that evaluates to a value works—variables, function calls, math, ternaries:

function Profile() {
  const user = { name: "Ada", logins: 42 };
  return (
    <p>
      {user.name} has logged in {user.logins} times
      ({user.logins > 10 ? "power user" : "newcomer"}).
    </p>
  );
}

Only values belong in { }. Statements like if or for don’t return a value, so they can’t go inline—use a ternary, &&, or compute above the return.

Attributes

JSX attributes use camelCase because they map to DOM properties, not HTML attributes. The two most common gotchas: class becomes className, and for becomes htmlFor.

<label htmlFor="email" className="field-label">Email</label>
<input id="email" type="email" tabIndex={0} />

String literals use quotes; any other value uses braces. Pass numbers, booleans, objects (for style), and functions (for event handlers) through { }.

Conditional rendering

There’s no special syntax—you use ordinary JavaScript:

function Inbox({ messages }) {
  if (messages.length === 0) {
    return <p>No new messages.</p>;
  }

  return (
    <div>
      {messages.length > 0 && <Badge count={messages.length} />}
      <MessageList items={messages} />
    </div>
  );
}

Use && for “render or nothing” and a ternary for “render A or B.” Beware that && with a numeric 0 left side renders the literal 0—guard with length > 0 rather than bare length.

Rendering lists and keys

Map an array to elements. Each item needs a stable, unique key so React can match elements across renders during reconciliation.

function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  );
}

Use a stable ID from your data as the key. Avoid the array index when items can reorder, insert, or delete—it ties keys to position and causes subtle state bugs and wasted re-renders.

Fragments

When you need to return siblings without adding a wrapper element to the DOM, use a fragment. The shorthand is an empty tag:

function Row() {
  return (
    <>
      <td>Name</td>
      <td>Email</td>
    </>
  );
}

Use the explicit <React.Fragment key={...}> form when you need a key on a fragment inside a list.

Best Practices

  • Keep JSX expressions small; if logic grows, compute values in variables above the return.
  • Always provide stable keys for list items, never the index for dynamic lists.
  • Prefer fragments over wrapper <div>s to keep the DOM lean.
  • Remember the HTML-to-JSX renames: className, htmlFor, tabIndex, camelCased event handlers.
  • Guard && conditions against falsy non-boolean values like 0 or "".
Last updated June 1, 2026
Was this helpful?