# TSX Fundamentals

Course: [React](https://www.devtutorials.ai/learn/react) · Section: React Fundamentals
Source: https://www.devtutorials.ai/learn/react/react-fundamentals/tsx-fundamentals

> What TSX is and why React uses it, TSX syntax, the rules every TSX element follows, putting values inside { }, comments, rendering elements to the page, and Fragments.

## TSX Introduction

**TSX** (TypeScript XML) lets you write HTML-like code inside a TypeScript file. Files that use it end in `.tsx`.

A React component uses TSX to describe what should appear on the screen. TSX is the TypeScript version of **JSX** (JavaScript XML).

### Real-World Example

A product page in an online shop shows the title, price and stock of one book. The developer writes the layout once in TSX: `<h2>{book.title}</h2>` and `<p>Price: ₹{book.price}</p>`.

When the page loads a different book, the same TSX shows the new values. No HTML is copied or changed by hand.

TSX is HTML-like code inside TypeScript. It describes what a React component shows on the screen.

## Why TSX?

Without TSX, you would build every element with a function call. TSX gives you a shorter way to write the same thing.

- **Easy to read** — The code looks like the page it creates.
- **HTML and logic together** — Values and conditions sit right next to the markup that uses them.
- **Type safety** — TypeScript warns you about mistakes, such as a missing prop, before the app runs.
- **Helpful errors** — The compiler points to the exact line of a broken tag.

### TSX Is Turned Into JavaScript

The browser cannot read TSX. Before the app runs, the TypeScript compiler turns every tag into a plain JavaScript function call.

These two lines create the same heading:

```tsx
// With TSX
const heading = <h1 className="title">Book Corner</h1>;

// What the compiler turns it into (simplified)
const heading = jsx("h1", { className: "title", children: "Book Corner" });
```

Every Run button in this course does the same thing: it compiles your TSX with the TypeScript compiler, then runs the result.

<figure className="react-fig ngc-fig">
<svg viewBox="0 0 640 130" role="img" aria-labelledby="fig-tsx-compile" style={{ width: "100%", maxWidth: 600, height: "auto" }}>
  <title id="fig-tsx-compile">Animation: TSX code goes through the TypeScript compiler, becomes JavaScript, and the browser runs it</title>
  <defs><marker id="tsx-a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0 0 L10 5 L0 10 z" style={{ fill: "var(--ngc-slate)" }} /></marker></defs>
  <g opacity="0"><animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.0;0.04;0.93;1" dur="8s" repeatCount="indefinite" /><rect x="10" y="30" width="130" height="56" rx="8" style={{ fill: "var(--ngc-blue-soft)", stroke: "var(--ngc-blue)", strokeWidth: 1.5 }} /><text x="75" y="54" textAnchor="middle" style={{ fill: "var(--ngc-blue)", fontSize: 12, fontWeight: 700, fontFamily: "var(--sans)" }}>TSX</text><text x="75" y="70" textAnchor="middle" style={{ fill: "var(--ngc-blue)", fontSize: 11, fontWeight: 400, fontFamily: "var(--mono)" }}>&lt;h1&gt;Hi&lt;/h1&gt;</text></g>
  <g opacity="0"><animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.18;0.22;0.93;1" dur="8s" repeatCount="indefinite" /><line x1="142" y1="58" x2="172" y2="58" style={{ stroke: "var(--ngc-slate)", strokeWidth: 2 }} markerEnd="url(#tsx-a)" /></g>
  <g opacity="0"><animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.22;0.26;0.93;1" dur="8s" repeatCount="indefinite" /><rect x="175" y="30" width="140" height="56" rx="8" style={{ fill: "var(--ngc-purple-soft)", stroke: "var(--ngc-purple)", strokeWidth: 1.5 }} /><text x="245" y="54" textAnchor="middle" style={{ fill: "var(--ngc-purple)", fontSize: 12, fontWeight: 700, fontFamily: "var(--sans)" }}>TypeScript</text><text x="245" y="70" textAnchor="middle" style={{ fill: "var(--ngc-purple)", fontSize: 11, fontWeight: 400, fontFamily: "var(--sans)" }}>compiler</text></g>
  <g opacity="0"><animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.4;0.44;0.93;1" dur="8s" repeatCount="indefinite" /><line x1="317" y1="58" x2="347" y2="58" style={{ stroke: "var(--ngc-slate)", strokeWidth: 2 }} markerEnd="url(#tsx-a)" /></g>
  <g opacity="0"><animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.44;0.48;0.93;1" dur="8s" repeatCount="indefinite" /><rect x="350" y="30" width="130" height="56" rx="8" style={{ fill: "var(--ngc-orange-soft)", stroke: "var(--ngc-orange)", strokeWidth: 1.5 }} /><text x="415" y="54" textAnchor="middle" style={{ fill: "var(--ngc-orange)", fontSize: 12, fontWeight: 700, fontFamily: "var(--sans)" }}>JavaScript</text><text x="415" y="70" textAnchor="middle" style={{ fill: "var(--ngc-orange)", fontSize: 11, fontWeight: 400, fontFamily: "var(--mono)" }}>jsx("h1", ...)</text></g>
  <g opacity="0"><animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.62;0.66;0.93;1" dur="8s" repeatCount="indefinite" /><line x1="482" y1="58" x2="512" y2="58" style={{ stroke: "var(--ngc-slate)", strokeWidth: 2 }} markerEnd="url(#tsx-a)" /></g>
  <g opacity="0"><animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.66;0.70;0.93;1" dur="8s" repeatCount="indefinite" /><rect x="515" y="30" width="115" height="56" rx="8" style={{ fill: "var(--ngc-green-soft)", stroke: "var(--ngc-green)", strokeWidth: 1.5 }} /><text x="572" y="54" textAnchor="middle" style={{ fill: "var(--ngc-green)", fontSize: 12, fontWeight: 700, fontFamily: "var(--sans)" }}>Browser</text><text x="572" y="70" textAnchor="middle" style={{ fill: "var(--ngc-green)", fontSize: 11, fontWeight: 400, fontFamily: "var(--sans)" }}>shows Hi</text></g>
  <text x="320" y="115" textAnchor="middle" style={{ fill: "var(--ngc-slate)", fontSize: 12, fontWeight: 700, fontFamily: "var(--sans)" }}>The browser never sees TSX. It runs the JavaScript.</text>
</svg>
<figcaption>TSX is compiled to JavaScript before the browser runs it.</figcaption>
</figure>

## Introduction to TSX Syntax

A TSX element looks like an HTML tag. You can store it in a variable, return it from a function, or pass it to another function.

      {welcome}
      <p>Fresh books every week.</p>
    </div>
  );
}
`,
    "main.tsx": `import { createRoot } from "react-dom/client";

createRoot(document.getElementById("root")!).render(<App />);
`,
  }}
/>

The variable `welcome` holds a TSX element. Writing `{welcome}` inside the `<div>` puts it on the page.

### Multi-Line TSX

When TSX spans more than one line, wrap it in round brackets `( )`. This tells TypeScript where the TSX starts and ends.

```tsx
return (
  <div>
    <h1>Book Corner</h1>
  </div>
);
```

## TSX Rules and Conventions

TSX looks like HTML, but it follows stricter rules. Breaking a rule gives an error when the code is compiled.

### Rule 1: Return One Parent Element

A component must return a single element at the top. Wrap sibling elements in one parent, such as a `<div>`.

```tsx
// Wrong: two elements at the top
return (
  <h1>Book Corner</h1>
  <p>Fresh books every week.</p>
);
```

The compiler stops with: `JSX expressions must have one parent element.`

<figure className="react-fig ngc-fig">
<svg viewBox="0 0 490 135" role="img" aria-labelledby="fig-tsx-rule1" style={{ width: "100%", maxWidth: 600, height: "auto" }}>
  <title id="fig-tsx-rule1">Animation: two top-level elements cause a compile error; wrapped in a Fragment, they compile</title>
  <g opacity="0"><animate attributeName="opacity" values="1;1;0;0;1" keyTimes="0;0.45;0.5;0.97;1" dur="9s" repeatCount="indefinite" /><rect x="30" y="30" width="170" height="34" rx="8" style={{ fill: "var(--ngc-red-soft)", stroke: "var(--ngc-red)", strokeWidth: 1.5 }} /><text x="115" y="51" textAnchor="middle" style={{ fill: "var(--ngc-red)", fontSize: 12, fontWeight: 700, fontFamily: "var(--mono)" }}>&lt;h1&gt;Book Corner&lt;/h1&gt;</text><rect x="30" y="72" width="170" height="34" rx="8" style={{ fill: "var(--ngc-red-soft)", stroke: "var(--ngc-red)", strokeWidth: 1.5 }} /><text x="115" y="93" textAnchor="middle" style={{ fill: "var(--ngc-red)", fontSize: 12, fontWeight: 700, fontFamily: "var(--mono)" }}>&lt;p&gt;Fresh books&lt;/p&gt;</text><rect x="250" y="45" width="220" height="46" rx="8" style={{ fill: "var(--ngc-red-soft)", stroke: "var(--ngc-red)", strokeWidth: 1.5 }} /><text x="360" y="64" textAnchor="middle" style={{ fill: "var(--ngc-red)", fontSize: 12, fontWeight: 700, fontFamily: "var(--sans)" }}>Error</text><text x="360" y="80" textAnchor="middle" style={{ fill: "var(--ngc-red)", fontSize: 11, fontWeight: 400, fontFamily: "var(--sans)" }}>must have one parent</text></g>
  <g opacity="0"><animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.5;0.55;0.95;1" dur="9s" repeatCount="indefinite" /><rect x="30" y="30" width="170" height="34" rx="8" style={{ fill: "var(--ngc-green-soft)", stroke: "var(--ngc-green)", strokeWidth: 1.5 }} /><text x="115" y="51" textAnchor="middle" style={{ fill: "var(--ngc-green)", fontSize: 12, fontWeight: 700, fontFamily: "var(--mono)" }}>&lt;h1&gt;Book Corner&lt;/h1&gt;</text><rect x="30" y="72" width="170" height="34" rx="8" style={{ fill: "var(--ngc-green-soft)", stroke: "var(--ngc-green)", strokeWidth: 1.5 }} /><text x="115" y="93" textAnchor="middle" style={{ fill: "var(--ngc-green)", fontSize: 12, fontWeight: 700, fontFamily: "var(--mono)" }}>&lt;p&gt;Fresh books&lt;/p&gt;</text><rect x="20" y="20" width="190" height="96" rx="10" style={{ fill: "none", stroke: "var(--ngc-green)", strokeWidth: 2, strokeDasharray: "6 4" }} /><text x="115" y="15" textAnchor="middle" style={{ fill: "var(--ngc-green)", fontSize: 12, fontWeight: 700, fontFamily: "var(--sans)" }}>&lt;&gt; ... &lt;/&gt; Fragment</text><rect x="250" y="45" width="220" height="46" rx="8" style={{ fill: "var(--ngc-green-soft)", stroke: "var(--ngc-green)", strokeWidth: 1.5 }} /><text x="360" y="64" textAnchor="middle" style={{ fill: "var(--ngc-green)", fontSize: 12, fontWeight: 700, fontFamily: "var(--sans)" }}>Compiled</text><text x="360" y="80" textAnchor="middle" style={{ fill: "var(--ngc-green)", fontSize: 11, fontWeight: 400, fontFamily: "var(--sans)" }}>one parent: the Fragment</text></g>
  <g opacity="0"><animate attributeName="opacity" values="1;1;0;0;1" keyTimes="0;0.45;0.5;0.97;1" dur="9s" repeatCount="indefinite" /><text x="245" y="130" textAnchor="middle" style={{ fill: "var(--ngc-red)", fontSize: 12, fontWeight: 700, fontFamily: "var(--sans)" }}>Step 1: two elements at the top, so the compiler stops</text></g><g opacity="0"><animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.5;0.55;0.95;1" dur="9s" repeatCount="indefinite" /><text x="245" y="130" textAnchor="middle" style={{ fill: "var(--ngc-green)", fontSize: 12, fontWeight: 700, fontFamily: "var(--sans)" }}>Step 2: wrap them in a Fragment, and it compiles</text></g>
</svg>
<figcaption>A component returns one parent. A Fragment groups the elements without adding a tag.</figcaption>
</figure>

### Rule 2: Close Every Tag

Every tag must be closed. Tags with no content, such as `<img>`, `<input>` and `<br>`, close themselves with `/>`.

```tsx
<img src="cover.png" alt="Book cover" />
<input placeholder="Search books" />
<br />
```

### Rule 3: Use className Instead of class

`class` is a reserved word in JavaScript. In TSX, write `className` to add a CSS class.

### Rule 4: Use camelCase for Attributes

Attributes with two words join them, with the second word capitalised. For example, `onclick` becomes `onClick` and `tabindex` becomes `tabIndex`.

### Rule 5: Start Component Names With a Capital Letter

`<BookCard />` is a component. `<bookcard />` is treated as an unknown HTML tag.

The lab below follows all five rules.

      <h1>Book Corner</h1>
      <hr />
      <br />
      <input placeholder="Search books" tabIndex={1} />
      <button onClick={() => console.log("Search clicked")}>Search</button>
    </div>
  );
}
`,
    "App.css": `.shop {
  border: 1px solid #cbd5e1;
  border-radius: 8px;
  padding: 12px;
}
`,
    "main.tsx": `import { createRoot } from "react-dom/client";

createRoot(document.getElementById("root")!).render(<App />);
`,
  }}
/>

Press **Search** in the preview. The console shows "Search clicked", because `onClick` connects the button to the function.

Try removing the `/` from `<br />` and press ▶ Run. The compiler shows an error and names the line.

## Embedding Expressions in TSX

Curly braces `{ }` let you put a JavaScript value inside TSX. Anything inside the braces is worked out first, and the result is shown on the page.

You can put these inside `{ }`:

- **Variables** — `{title}`
- **Maths** — `{price * 2}`
- **Function calls** — `{title.toUpperCase()}`
- **Conditions** — `{stock > 0 ? "In stock" : "Out of stock"}`

      <h2>{title}</h2>
      <p>Price: {formatPrice(price)}</p>
      <p>Two copies: {formatPrice(price * 2)}</p>
      <p>Shout it: {title.toUpperCase()}</p>
      <p>{stock > 0 ? "In stock" : "Out of stock"}</p>
    </div>
  );
}
`,
    "main.tsx": `import { createRoot } from "react-dom/client";

createRoot(document.getElementById("root")!).render(<App />);
`,
  }}
/>

The last line shows "Out of stock" because `stock` is `0`. Change `stock` to `5` and press ▶ Run.

### Expressions in Attributes

You can also use `{ }` for attribute values. Use quotes for fixed text and braces for values.

```tsx
<img src={coverUrl} alt={title} />
<input maxLength={20} />
```

### What You Cannot Put Inside { }

Only **expressions** work inside `{ }`. An expression is code that produces a value. Statements such as `if` and `for` do not produce a value, so they cause an error.

```tsx
// Wrong
<p>{if (stock > 0) { "In stock" }}</p>

// Correct
<p>{stock > 0 ? "In stock" : "Out of stock"}</p>
```

Inside `{ }`, use a value, not an instruction. Use the `? :` operator instead of `if`.

## TSX Comments

Inside TSX, a comment goes inside curly braces: `{/* comment */}`. Outside TSX, in normal TypeScript code, use `//` as usual.

      <h1>{shopName}</h1>

        A comment can also
        span several lines.
      */}
      <p>Open every day, 9 am to 9 pm.</p>
    </div>
  );
}
`,
    "main.tsx": `import { createRoot } from "react-dom/client";

createRoot(document.getElementById("root")!).render(<App />);
`,
  }}
/>

The preview shows only the heading and the paragraph. The comments are removed when the code is compiled.

## Rendering TSX Elements

**Rendering** means showing a TSX element on the page. React needs two things for this: a place on the page, and the element to show there.

1. `index.html` has an empty box: `<div id="root"></div>`.
2. `createRoot` connects React to that box.
3. `render` shows the element inside the box.

```tsx
const root = createRoot(document.getElementById("root")!);
root.render(<App />);
```

The `!` after `getElementById(...)` tells TypeScript that the element is sure to exist.

<figure className="react-fig ngc-fig">
<svg viewBox="0 0 620 175" role="img" aria-labelledby="fig-tsx-render" style={{ width: "100%", maxWidth: 600, height: "auto" }}>
  <title id="fig-tsx-render">Animation: createRoot finds the empty root div, and render puts the App element inside it</title>
  <rect x="300" y="10" width="300" height="130" rx="10" style={{ fill: "var(--ngc-win)", stroke: "var(--ngc-slate)", strokeWidth: 1.5 }} /><text x="450" y="30" textAnchor="middle" style={{ fill: "var(--ngc-slate)", fontSize: 12, fontWeight: 700, fontFamily: "var(--sans)" }}>index.html</text><rect x="320" y="42" width="260" height="86" rx="8" style={{ fill: "none", stroke: "var(--ngc-blue)", strokeWidth: 2, strokeDasharray: "6 4" }} /><text x="450" y="60" textAnchor="middle" style={{ fill: "var(--ngc-blue)", fontSize: 12, fontFamily: "var(--mono)" }}>&lt;div id="root"&gt;</text>
  <rect x="20" y="55" width="150" height="50" rx="8" style={{ fill: "var(--ngc-purple-soft)", stroke: "var(--ngc-purple)", strokeWidth: 1.5 }} /><text x="95" y="76" textAnchor="middle" style={{ fill: "var(--ngc-purple)", fontSize: 12, fontWeight: 700, fontFamily: "var(--sans)" }}>&lt;App /&gt;</text><text x="95" y="92" textAnchor="middle" style={{ fill: "var(--ngc-purple)", fontSize: 11, fontWeight: 400, fontFamily: "var(--mono)" }}>the element</text>
  <defs><marker id="tsx-r" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0 0 L10 5 L0 10 z" style={{ fill: "var(--ngc-purple)" }} /></marker></defs><g opacity="0"><animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.2;0.25;0.6;0.65" dur="8s" repeatCount="indefinite" /><path d="M 172 80 Q 240 40 318 80" style={{ fill: "none", stroke: "var(--ngc-purple)", strokeWidth: 2 }} markerEnd="url(#tsx-r)" /><text x="240" y="45" textAnchor="middle" style={{ fill: "var(--ngc-purple)", fontSize: 12, fontWeight: 700, fontFamily: "var(--sans)" }}>root.render(...)</text></g>
  <g opacity="0"><animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.33;0.36;0.95;1" dur="8s" repeatCount="indefinite" /><g><rect x="20" y="55" width="150" height="50" rx="8" style={{ fill: "var(--ngc-green-soft)", stroke: "var(--ngc-green)", strokeWidth: 1.5 }} /><text x="95" y="76" textAnchor="middle" style={{ fill: "var(--ngc-green)", fontSize: 12, fontWeight: 700, fontFamily: "var(--sans)" }}>Welcome</text><text x="95" y="92" textAnchor="middle" style={{ fill: "var(--ngc-green)", fontSize: 11, fontWeight: 400, fontFamily: "var(--sans)" }}>Book Corner</text><animateTransform attributeName="transform" type="translate" values="0 0;0 0;345 15;345 15" keyTimes="0;0.35;0.6;1" dur="8s" repeatCount="indefinite" /></g></g>
  <g opacity="0"><animate attributeName="opacity" values="1;1" keyTimes="0;1" dur="8s" repeatCount="indefinite" /><text x="310" y="165" textAnchor="middle" style={{ fill: "var(--ngc-slate)", fontSize: 12, fontWeight: 700, fontFamily: "var(--sans)" }}>1. createRoot finds the box. 2. render puts the element inside it.</text></g>
</svg>
<figcaption>React shows your element inside the empty div id="root" box.</figcaption>
</figure>

### Rendering Updates the Page

Calling `render` again with a new element updates the page. React changes only the parts that are different.

      <h2>Book Corner</h2>
      <p>Visitors today: {visitors}</p>
    </div>
  );
}

showCounter();
setInterval(showCounter, 1000);
`,
  }}
/>

The number goes up every second. Each call to `render` creates a new element, and React updates only the number.

In real apps, you call `render` once, for `<App />`. Components then update themselves with **state**, which you will learn in a later lesson.

## Fragments in TSX

A component can return only one element. If you have two or more elements, you must put them inside one wrapper.

A **Fragment** is a wrapper that does not show on the page. You write it as `<>` at the start and `</>` at the end.

You can also use a `<div>` as the wrapper. But a `<div>` is added to the page as an extra tag. A Fragment adds nothing.

<figure className="react-fig ngc-fig">
<svg viewBox="0 0 510 180" role="img" aria-labelledby="fig-tsx-frag" style={{ width: "100%", maxWidth: 560, height: "auto" }}>
  <title id="fig-tsx-frag">Comparison: a div wrapper adds an extra node to the page, a Fragment adds none</title>
  <line x1="255" y1="10" x2="255" y2="170" style={{ stroke: "var(--line)", strokeDasharray: "4 5" }} /><text x="110" y="18" textAnchor="middle" style={{ fill: "var(--ngc-orange)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>With a div wrapper</text><rect x="60" y="30" width="100" height="28" rx="8" style={{ fill: "var(--ngc-purple-soft)", stroke: "var(--ngc-purple)", strokeWidth: 1.5 }} /><text x="110" y="48" textAnchor="middle" style={{ fill: "var(--ngc-purple)", fontSize: 12, fontWeight: 700, fontFamily: "var(--mono)" }}>div.shelf</text><line x1="110" y1="58" x2="110" y2="72" style={{ stroke: "var(--ngc-slate)", strokeWidth: 2 }} /><g><rect x="60" y="72" width="100" height="28" rx="8" style={{ fill: "var(--ngc-orange-soft)", stroke: "var(--ngc-orange)", strokeWidth: 1.5 }} /><text x="110" y="90" textAnchor="middle" style={{ fill: "var(--ngc-orange)", fontSize: 12, fontWeight: 700, fontFamily: "var(--mono)" }}>div (extra)</text><animate attributeName="opacity" values="1;0.4;1" dur="2.0s" repeatCount="indefinite" /></g><line x1="90" y1="100" x2="60" y2="114" style={{ stroke: "var(--ngc-slate)", strokeWidth: 2 }} /><line x1="130" y1="100" x2="160" y2="114" style={{ stroke: "var(--ngc-slate)", strokeWidth: 2 }} /><rect x="15" y="114" width="90" height="28" rx="8" style={{ fill: "var(--ngc-blue-soft)", stroke: "var(--ngc-blue)", strokeWidth: 1.5 }} /><text x="60" y="132" textAnchor="middle" style={{ fill: "var(--ngc-blue)", fontSize: 12, fontWeight: 700, fontFamily: "var(--mono)" }}>h2</text><rect x="115" y="114" width="90" height="28" rx="8" style={{ fill: "var(--ngc-blue-soft)", stroke: "var(--ngc-blue)", strokeWidth: 1.5 }} /><text x="160" y="132" textAnchor="middle" style={{ fill: "var(--ngc-blue)", fontSize: 12, fontWeight: 700, fontFamily: "var(--mono)" }}>p</text><text x="110" y="165" textAnchor="middle" style={{ fill: "var(--ngc-orange)", fontSize: 12, fontWeight: 700, fontFamily: "var(--sans)" }}>3 tags: one extra div</text>
  <text x="400" y="18" textAnchor="middle" style={{ fill: "var(--ngc-green)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>With a Fragment</text><rect x="350" y="30" width="100" height="28" rx="8" style={{ fill: "var(--ngc-purple-soft)", stroke: "var(--ngc-purple)", strokeWidth: 1.5 }} /><text x="400" y="48" textAnchor="middle" style={{ fill: "var(--ngc-purple)", fontSize: 12, fontWeight: 700, fontFamily: "var(--mono)" }}>div.shelf</text><line x1="380" y1="58" x2="350" y2="72" style={{ stroke: "var(--ngc-slate)", strokeWidth: 2 }} /><line x1="420" y1="58" x2="450" y2="72" style={{ stroke: "var(--ngc-slate)", strokeWidth: 2 }} /><rect x="305" y="72" width="90" height="28" rx="8" style={{ fill: "var(--ngc-blue-soft)", stroke: "var(--ngc-blue)", strokeWidth: 1.5 }} /><text x="350" y="90" textAnchor="middle" style={{ fill: "var(--ngc-blue)", fontSize: 12, fontWeight: 700, fontFamily: "var(--mono)" }}>h2</text><rect x="405" y="72" width="90" height="28" rx="8" style={{ fill: "var(--ngc-blue-soft)", stroke: "var(--ngc-blue)", strokeWidth: 1.5 }} /><text x="450" y="90" textAnchor="middle" style={{ fill: "var(--ngc-blue)", fontSize: 12, fontWeight: 700, fontFamily: "var(--mono)" }}>p</text><g opacity="0"><animate attributeName="opacity" values="1;1;0;0;1" keyTimes="0;0.4;0.5;0.95;1" dur="6s" repeatCount="indefinite" /><rect x="300" y="66" width="200" height="40" rx="10" style={{ fill: "none", stroke: "var(--ngc-green)", strokeWidth: 2, strokeDasharray: "6 4" }} /><text x="400" y="124" textAnchor="middle" style={{ fill: "var(--ngc-green)", fontSize: 12, fontWeight: 700, fontFamily: "var(--sans)" }}>&lt;&gt; groups them, then disappears</text></g><text x="400" y="165" textAnchor="middle" style={{ fill: "var(--ngc-green)", fontSize: 12, fontWeight: 700, fontFamily: "var(--sans)" }}>2 tags: no extra node</text>
</svg>
<figcaption>A div wrapper stays on the page. A Fragment leaves nothing behind.</figcaption>
</figure>

      <h2>Atomic Habits</h2>
      <p>by James Clear</p>
    </>
  );
}

  return (
    <div className="shelf">
    </div>
  );
}
`,
    "main.tsx": `import { createRoot } from "react-dom/client";

createRoot(document.getElementById("root")!).render(<App />);

// Print the real HTML that React created.
setTimeout(() => {
  console.log(document.getElementById("root")!.innerHTML);
}, 100);
`,
  }}
/>

Look at the console. The `<h2>` and `<p>` sit directly inside `<div class="shelf">`. The Fragment left no tag of its own.

### The Long Form: Fragment

Sometimes a Fragment needs extra information, called a `key`. React uses a key when you show a list of items. You will learn about it later.

The short form `<>` cannot hold a key. So in that case, use the long form: import `Fragment` from React and write `<Fragment>` and `</Fragment>`.

```tsx
import { Fragment } from "react";

<Fragment key={book.id}>
  <dt>{book.title}</dt>
  <dd>{book.author}</dd>
</Fragment>
```

Easy Way to Remember: need a wrapper but no extra tag? Use `<>` and `</>`.

## Common mistakes

<div className="tscroll">

| Mistake | What you see | Fix |
|---|---|---|
| Returning two elements side by side | `JSX expressions must have one parent element.` | Wrap them in `<>` and `</>` or a `<div>` |
| Leaving a tag open, such as `<br>` | `JSX element 'br' has no corresponding closing tag.` | Close it: `<br />` |
| Writing `class="title"` | The class may not work, and TypeScript warns | Write `className="title"` |
| Writing `onclick` in lower case | The click does nothing | Write `onClick` |
| Putting `if` inside `{ }` | `Expression expected.` | Use `condition ? a : b` |
| Writing a comment as `<!-- -->` | The comment shows on the page or causes an error | Write `{/* comment */}` |
| Forgetting `{ }` around a variable | The page shows the word `title`, not its value | Write `{title}` |
| Multi-line TSX without `( )` after `return` | The component shows nothing | Wrap the TSX in `( )` |

</div>

## Check Yourself

## Exercises

## What to Remember

- TSX is HTML-like code in TypeScript. The compiler turns it into JavaScript before the app runs.
- Use `{ }` to put values in TSX, return one parent element, and close every tag.
- Use a Fragment `<> </>` to group elements without adding an extra tag.

Next: Components
