# Elements HTML vs JSX JSX is a syntax extension to JavaScript for writing UI markup inside JavaScript and TypeScript files. Its [specification](https://github.com/facebook/jsx), published by Meta alongside React, calls it "an XML-like syntax extension to ECMAScript without any defined semantics," and says it is not meant to be implemented by browsers or added to the language. A compiler such as Babel, TypeScript or esbuild turns each tag into a function call: `React.createElement(...)` in the classic transform, or `_jsx(...)` imported from `react/jsx-runtime` in the current one ([the JSX transform](https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html)). TSX is the same syntax in TypeScript, in `.tsx` files with the `jsx` compiler option turned on ([TypeScript handbook](https://www.typescriptlang.org/docs/handbook/jsx.html)). React is the main user, and [Preact](https://preactjs.com/guide/v10/differences-to-react/), [Solid](https://docs.solidjs.com/concepts/understanding-jsx) and others compile JSX for their own runtimes. JSX fills the role of a template language: it is what pages and components are written in. Elements has a template language of its own. Elements is an integrated app environment, built for people and their agents: a project server that runs while you work, with a package installer, a test runner, a bundled Postgres and deploy in the same system, and a default framework that just works. Pages and components in that framework are written in [Elements HTML](/learn/man/html), a language extension to HTML plus a runtime. An `.ehtml` file is standard HTML with TypeScript expressions in `{...}`, typed templates declared with template constructors, and a few `e:` attributes: `e:if`, `e:for` and `e:switch`. The Elements compiler type checks each template with the rest of the app and emits JavaScript, for the server that renders the page and for the browser that runs it, and the Elements runtime ships to the browser with it. JSX is JavaScript dressed as HTML, so HTML has to be translated before it goes in. In React, `class` becomes `className`, `for` becomes `htmlFor` and styles become objects, while conditions and loops become ternaries and `.map()` calls. JSX also has no meaning of its own. State, input binding, server rendering and updates come from the library that compiles it and the framework around that, so one syntax behaves differently from one project to the next, and every seam between those parts is a place for a person or an agent to go wrong. Elements HTML runs the other way: it is HTML that runs TypeScript. Markup copied from a mockup or from MDN works as written, inputs bind both ways, and one compiler, one runtime and one build cover every template. Each page arrives from the server as complete HTML, and the same page is fully reactive in the browser, with no compromise. ## At a Glance | | Elements HTML | JSX | |---|---|---| | What it is | A language extension to HTML plus a runtime, part of the Elements app framework | A syntax extension to JavaScript "without any defined semantics" ([spec](https://github.com/facebook/jsx)) | | Compiles to | JavaScript for the server render and the browser, from the Elements compiler | Function calls such as `React.createElement` or `_jsx`, from Babel, TypeScript or esbuild | | Attributes | [As in HTML](/learn/man/html/attributes): `class`, `for`, `style`, `tabindex`, `stroke-width` | In React, DOM property names: `className`, `htmlFor`, `tabIndex`, `strokeWidth` | | Plain HTML | Pastes in as written | Converted first; React's docs recommend a converter | | Conditions | [`e:if`, `e:elseif`, `e:else`, `e:switch`](/learn/man/html/directives) | Ternaries and `&&` | | Loops | [`e:for`](/learn/man/html/directives), keyed by `id` by default | `.map()`, with a `key` on every element | | Form inputs | [Two-way bound](/learn/man/html/attributes) by default | In React, a `value` and an `onChange` handler per input | | Typed components | [Template constructors](/learn/man/html/attributes) on the opening tag | A props type or interface on a function | | Checking | [One build](/learn/man/build) with tests and migrations, and the same checker in the editor | `tsc` or your editor; Vite's dev server does not type check | | Rendering | [On the server](/learn/man/html/server) for every page, then reactive in the browser | Set by the library and framework: a blank page until JavaScript runs, or server HTML that must be hydrated | ## Elements HTML Is HTML React's own guide to JSX lists the rules that make it stricter than HTML ([writing markup with JSX](https://react.dev/learn/writing-markup-with-jsx)). Every tag has to be closed, so `` becomes ``. A component returns a single root element. Most attributes are camelCase, and "since `class` is a reserved word, in React you write `className` instead." The label's `for` attribute is `htmlFor`, because "React uses the standard DOM property names (`htmlFor`) instead of HTML attribute names," and `style` takes an object with camelCase property names ([common components](https://react.dev/reference/react-dom/components/common)). The guide's advice for existing markup is to run it through a converter: "Converting all these attributes in existing markup can be tedious!" Elements HTML has nothing to convert. A tag is an HTML tag, an attribute is an HTML attribute, and void elements such as `` and `
` need no slash. This form, pasted from a mockup, compiles and renders as written: ```ehtml // app/pages/signup/template.ehtml (Elements)

We never share your email.

Logo
``` Dynamic values go in braces anywhere HTML accepts a value. `class` also takes an array that drops falsy entries, and `style` also takes an object whose keys are CSS property names as CSS writes them ([attributes](/learn/man/html/attributes)). Braces always mark TypeScript, and `e:literal` keeps an element's braces as text, for code samples. A page is a template named ``, so attributes on it land on the document element. ## The Same Template in JSX and Elements HTML A short list with a labeled input shows the difference line for line. In React, the component holds the input's text in state, wires an `onChange` handler to keep it there, renames two attributes, and writes the condition and the loop as JavaScript expressions: ```tsx // app/components/Invites.tsx (React) import { useState } from "react"; import type { Invite } from "../types"; export function Invites({ invites }: { invites: Invite[] }) { const [email, setEmail] = useState(""); return (
setEmail(e.target.value)} /> {invites.length > 0 ? ( ) : (

No invites yet.

)}
); } ``` In Elements, the same template is HTML, with the condition and the loop written as attributes: ```ehtml // app/pages/invites/template.ehtml (Elements) import type { Invite } from "#app/shared/services/invites";

No invites yet.

``` The attributes are declared on the opening tag, the input is bound to `form.email` in both directions, and the list renders on the server with its rows in the first response. ## Conditions and Loops Are Attributes JSX accepts only expressions between braces, so it has no `if` and no `for`. React's docs write conditions as `{cond ? : }` and `{cond && }` ([conditional rendering](https://react.dev/learn/conditional-rendering)), and the same page warns against a mistake the syntax invites: "Don't put numbers on the left side of `&&`," because `messageCount &&

New messages

` "really renders the `0` itself!" Lists are `.map()` calls, and "JSX elements directly inside a `map()` call always need keys!" ([rendering lists](https://react.dev/learn/rendering-lists)). A nested condition inside a loop inside a condition becomes nested parentheses inside the markup. Elements HTML puts control flow on the element it controls ([directives](/learn/man/html/directives)). `e:if`, `e:elseif` and `e:else` sit on sibling elements, `e:switch` holds a value and its children carry `e:case` and `e:default`, and `e:for` iterates any iterable with `of` or `in`. A condition decides whether its element renders and is never rendered itself, so there is no stray `0` on the page. Rows key themselves by their `id` field, and `e:key` takes a typed key function for rows without one. When the list changes, the runtime patches the DOM at the row level, so a row present before and after keeps its elements. ## Inputs Bind Both Ways A form is mostly inputs, and in React each one is two pieces of wiring. "Every controlled input needs an `onChange` event handler that synchronously updates its backing value," says React's [input reference](https://react.dev/reference/react-dom/components/input), and the same page warns: "If you pass `value` without `onChange`, it will be impossible to type into the input." In Elements HTML, `value`, `checked` and `group` are two-way bindings on ``, `