Command Palette

Search for a command to run...

Hectal
PHASE 1Beginner ~20 min· topic 6 of 7

Topic 1.6

Converting HTML to JSX

In one line

To turn HTML into JSX you rename attributes (class, for, camelCase, SVG names like stroke-width), close every tag, swap <!-- --> comments for {/* */}, and know how JSX treats entities, Unicode and whitespace; data-* and aria-* attributes stay as they are.

Think of it like this

Translating a recipe from British to American English. Most words stay the same, but a few must change: "biscuit" becomes "cookie", grams become cups. Translating HTML to JSX is the same: most of it stays, and a short, learnable list of things changes.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Attribute rename
Writing an HTML attribute with its JSX name, like tabindex → tabIndex.
data-* attribute
Your own information stored on an element, like data-id="7". Stays dashed in JSX.
aria-* attribute
Information for screen readers, like aria-label="Close". Stays dashed in JSX.
HTML entity
A code for a special character, like &copy; for ©. Works in JSX text, not in braces.
Unicode escape
A JavaScript way to write any character by number: "\u20b9" is ₹.
JSX comment
{/* … */}: a JavaScript comment inside braces. HTML comments don't work.
Whitespace
Spaces, tabs and line breaks. JSX removes line breaks between elements.
Inline SVG
An <svg> drawing written right in the markup, often used for icons.

Step by step

01Start from real HTML

A small newsletter form from an old page. Count the things that must change before you read on.

signup.htmlwhole filehtml
<!-- Newsletter box -->
<form class="box" onsubmit="return send()">
  <label for="mail">Email</label>
  <input id="mail" type="email" tabindex="1" autofocus>
  <button class="btn" data-track="signup" aria-label="Subscribe">&rarr;</button>
</form>

02Fix comments and closing tags

<!-- … --> becomes {/* … */} (inside the JSX) or a // comment above it. <input …> becomes <input … />.

03Rename attributes, keep data-* and aria-*

class → className, for → htmlFor, tabindex → tabIndex, autofocus → autoFocus. The string onsubmit handler becomes a function prop (Phase 4 and 5 show how). data-track and aria-label stay exactly as they were. The entity &rarr; works as is.

Signup.jsxwhole filejsx
function Signup() {
  return (
    <form className="box">
      {/* Newsletter box */}
      <label htmlFor="mail">Email</label>
      <input id="mail" type="email" tabIndex={1} autoFocus />
      <button className="btn" data-track="signup" aria-label="Subscribe">&rarr;</button>
    </form>
  );
}

04Check the whitespace

Where text and an element are on different lines, JSX joins them with no space. Read your converted markup and add {" "} where words would run together.

Check the whitespacediagram
Rendering diagram…

05Bring in SVG icons

Paste the SVG, camelCase the dashed attributes, drop xmlns, and add aria-hidden="true" if the icon is decoration.

CheckIcon.jsxwhole filejsx
// HTML: <svg viewBox="0 0 24 24"><path d="M4 12l5 5L20 6" stroke-width="2" stroke-linecap="round" fill="none" stroke="currentColor"/></svg>
function CheckIcon() {
  return (
    <svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true">
      <path d="M4 12l5 5L20 6" strokeWidth={2} strokeLinecap="round" fill="none" stroke="currentColor" />
    </svg>
  );
}

Try it yourself

  1. 1

    Remove the forced space

    In the receipt, delete {" "} from line e. Predict the last log line. (e: "Total:₹40", the same as d.)

  2. 2

    Change the rating

    Render <Rating stars={5} />. What does a screen reader hear? ("5 out of 5 stars": the label is built from the prop.)

  3. 3

    Convert an HTML snippet yourself

    Convert <p class="note">Price&nbsp;drop! <a href="/sale" tabindex="0">See&nbsp;deals&rarr;</a></p> into JSX inside the receipt, then compare with the cheat sheet. Only class and tabindex change.

Code & diagrams

Entities, Unicode, comments and whitespace New tab

Lines a and b are identical: entities in JSX text, Unicode escapes inside a JavaScript string. Line c joined two text lines with one space. Line d lost the space between `Total:` and the price because a line break between text and a tag is removed; line e puts it back with `{" "}`.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

a: "Tea & biscuits — ₹40 © 2024"
b: "Tea & biscuits — ₹40 © 2024"
c: "Thank you for visiting"
d: "Total:₹40"
e: "Total: ₹40"
Inline SVG icons with aria-* and data-* New tab

You write `strokeWidth` and `strokeLinejoin`; React writes `stroke-width` and `stroke-linejoin`. `aria-label` and `data-stars` are written dashed and arrive unchanged. The stars are `aria-hidden`, and the wrapper's label tells a screen reader the rating in words.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

3 out of 5 stars | 3
2 round
0 0 24 24
HTML → JSX cheat sheetjsx
// HTML                          // JSX
class="x"                        className="x"
for="email"                      htmlFor="email"
tabindex="0"                     tabIndex={0}
maxlength="40"                   maxLength={40}
readonly                         readOnly
onclick="save()"                 onClick={save}
style="color: red"               style={{ color: "red" }}
<br>  <img src="a.png">          <br />  <img src="a.png" />
<!-- note -->                    {/* note */}
stroke-width="2"  (SVG)          strokeWidth={2}
xlink:href="#a"   (SVG)          xlinkHref="#a"   (or plain href="#a")
data-id="7"                      data-id="7"       (unchanged)
aria-label="Close"               aria-label="Close" (unchanged)

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Keep an HTML comment

In the receipt, replace the {/* … */} line with <!-- old HTML comment -->.

terminal
$ Console
── what you'll see ──
Uncaught SyntaxError: Unexpected token (6:8)

Break #2

Paste SVG attributes unchanged

In StarIcon, change strokeWidth={2} to stroke-width="2".

terminal
$ Console
── what you'll see ──
Invalid DOM property `stroke-width`. Did you mean `strokeWidth`?

Break #3

camelCase an aria attribute

In Rating, change aria-label= to ariaLabel=.

terminal
$ Console
── what you'll see ──
Invalid ARIA attribute `ariaLabel`. Did you mean `aria-label`?

Myth vs fact

Myth

HTML pasted into a component just works.

Fact

It usually needs a few changes: attribute names, closing tags, comments, style strings and event handlers. The rules are short and mechanical, and the warnings point at each one.

Myth

Everything in JSX is camelCase.

Fact

data-* and aria-* keep their dashes, and custom properties in style objects ("--x") keep theirs too.

Myth

&nbsp; doesn't work in JSX.

Fact

HTML entities work in JSX text. They don't work inside braces (JavaScript strings), where you use the character or \u00a0.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    JSX's whitespace rule (from the JSX spec): lines of text are trimmed, lines that are empty after trimming are dropped, and the remaining lines are joined with single spaces. That's why indentation never leaks into your text, and why breaks between text and tags vanish.

  • ▸

    JSX supports all named HTML entities in text and in quoted attribute values (title="a &amp; b"), but not inside braces. JSX string attributes also don't process JavaScript escapes: title="a\u00a9b" keeps the backslash literally; write title={"a\u00a9b"} for the escape.

  • ▸

    For many SVG icons, import them as components with a build plugin (SVGR in Vite: import Logo from "./logo.svg?react") instead of hand-converting. Icon libraries such as lucide-react ship each icon as a ready React component.

Remember this

  1. 1

    Attribute renames: class → className, for → htmlFor, and multi-word attributes become camelCase: tabindex → tabIndex, maxlength → maxLength, readonly → readOnly, autocomplete → autoComplete, contenteditable → contentEditable, srcset → srcSet. Event attributes become camelCase props that take a function, not a string: onclick="save()" becomes onClick={save} (events are Phase 4). The style string becomes an object (Topic 1.5).

  2. 2

    **data-* and aria-* keep their dashes.** data-id="7", aria-label="Close" and aria-expanded={open} are written exactly as in HTML. Custom data attributes are for your own information on elements; ARIA attributes describe elements to screen readers and other assistive technology (Phase 18 and the /html course). Writing ariaLabel is a mistake that React warns about.

  3. 3

    Comments: HTML's <!-- … --> is a syntax error in JSX. Inside JSX, write a JavaScript comment wrapped in braces: {/* note */}. Outside JSX (in the function body) normal // comments work.

  4. 4

    Entities and Unicode: inside JSX text, HTML entities work: &amp;, &copy;, &mdash;, &nbsp;, &#8377; (₹). Inside braces you are in JavaScript, where entities are just text, so use the character itself ("©") or a JavaScript escape ("\u00a9"). Since source files are UTF-8, typing the real character (₹, ©, —) is usually the clearest choice.

  5. 5

    Whitespace rules differ from HTML. Text on one line keeps its spaces. A line break plus indentation between pieces is removed entirely: a line ending in Total: followed by <strong> on the next line renders as Total:₹40, with no space. Line breaks *inside* a run of text become one space. To force a space where lines join, write {" "}.

  6. 6

    Inline SVG works in JSX, which is handy for icons. Copy the <svg>, then camelCase its dashed attributes: stroke-width → strokeWidth, stroke-linejoin → strokeLinejoin, fill-rule → fillRule, clip-path → clipPath. viewBox is already camelCase; xmlns can be removed. Decorative icons get aria-hidden="true" so screen readers skip them; meaningful ones need a label.

Explain it without notes

01

List the changes needed to turn an HTML snippet into JSX.

02

How do comments work in JSX?

03

Explain JSX's whitespace rules with an example.

04

Why do data-* and aria-* keep their dashes when everything else is camelCase?

Practice

01

Convert to JSX: <!-- card --><div class="card" data-id="3" tabindex="0"><img src="p.png" alt=""><p>Price:&nbsp;&#8377;99</p></div>.

02

Convert this icon to a component: <svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" stroke-width="2" stroke="black" fill="none"/></svg>, hidden from screen readers.

Trade-offs

  • ↔

    Hand-converting HTML is fine for a snippet; for big templates or many SVG files, an automated converter (an HTML-to-JSX tool or SVGR) is faster and avoids typos, but still review the output for accessibility and whitespace.

Done when you can

  • I can convert an HTML snippet to JSX without warnings.

  • I write comments in JSX as {/* */}.

  • I know where entities work and where to use Unicode characters.

  • I can predict and fix whitespace between text and tags.

  • I can paste an SVG icon and camelCase its attributes.

  • I keep data-* and aria-* dashed.