In a React project, what JavaScript does the JSX expression `<Button color="blue">Save</Button>` compile to, and what does that compiled call return at runtime?
answer
- not a template language
- every tag becomes a function call
- children ride inside props
- automatic runtime vs React.createElement
- returns a plain object with type and props
basics
~20 sJSX is sugar for a function call. With React's automatic runtime it compiles to jsx(Button, { color: 'blue', children: 'Save' }), imported from react/jsx-runtime. That call returns a plain, immutable JavaScript object describing what to render, not a DOM node.
solid answer
~40 sJSX is not understood by any browser; a compiler (Babel or SWC) rewrites every tag into a function call. Under the **automatic runtime** — the default in modern toolchains — `<Button color="blue">Save</Button>` becomes `_jsx(Button, { color: "blue", children: "Save" })`, with `_jsx` imported from `react/jsx-runtime`; children are just another entry in the props object. Under the older **classic runtime** the same tag becomes `React.createElement(Button, { color: "blue" }, "Save")`, with children passed as trailing arguments, which is why that mode required `React` to be in scope. Either way the call returns a **React element**: a plain object carrying `type` (here the `Button` function, or a string like `"div"` for a host tag), `props`, `key`, and an internal `$$typeof` marker. It is an inert description — React calls `Button` later, during render.
code
javascript · 16 linesimport { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
function Button({ color, children }) {
return _jsx("button", { style: { color }, children });
}
const tree = _jsxs("div", {
children: [
_jsx(Button, { color: "blue", children: "Save" }, "save"),
_jsx(Button, { color: "red", children: "Delete" }, "delete"),
],
});
console.log(tree.type); // "div"
console.log(tree.props.children.length); // 2
console.log(tree.props.children[0].type === Button); // truego deeper
Be ready to say plainly that JSX is compiled away before the browser sees it, and that a tag becomes a function call returning an object React later renders.
Write out both outputs on demand: the classic React.createElement(type, props, ...children) and the automatic jsx(type, props, key) from react/jsx-runtime, and point out that children live inside props.
Use the desugaring to explain downstream behaviour — spread precedence, why key is not readable from props, why creating an element never runs the component — rather than reciting the transform on its own.
Own the build-level consequences: which transform each package in a monorepo compiles with, what jsxImportSource lets a styling or instrumentation library intercept, and what a change to that pipeline costs across teams.
## JSX is a syntax, not a runtime feature No JavaScript engine understands JSX. It is a syntax extension that a build step — Babel's `@babel/plugin-transform-react-jsx` (usually via `@babel/preset-react`), or SWC in Next.js, or esbuild/SWC behind Vite's React plugin — rewrites into ordinary function calls before the code ever reaches the browser. Being able to write out that rewritten code is the single fastest way to show you understand React instead of having memorized a template language. ## The two transforms There are two compilation modes, and both are still in use. The **classic** runtime, which is what React used for years: ```js // source <Button color="blue">Save</Button> // output React.createElement(Button, { color: "blue" }, "Save"); ``` Attributes become a props object; children become trailing arguments. Because the emitted code names `React`, every file containing JSX had to have `React` imported and in scope. The **automatic** runtime, introduced in React 17 and what current toolchains emit: ```js // output import { jsx as _jsx } from "react/jsx-runtime"; _jsx(Button, { color: "blue", children: "Save" }); ``` Here the compiler injects the import itself, and children are folded into the props object under the `children` key. That is not a cosmetic difference: `props.children` is exactly where children live at runtime in both modes, so the automatic output simply makes the data shape visible. ## What the call returns Both `jsx(...)` and `React.createElement(...)` return a **React element**: a frozen plain object, roughly ```js { $$typeof: Symbol.for("react.element"), // internal brand type: Button, // function/class, or "div" for a host tag key: null, props: { color: "blue", children: "Save" } } ``` Three things follow from that shape. First, `type` is the *value* the tag resolved to — a function reference for a component, a plain string for a DOM tag. Second, `key` is lifted out of the attributes into its own field, so a component cannot read it from `props`. Third, calling `jsx` does not call `Button`; it only records that `Button` should be called with those props. React invokes `type` itself while rendering, which is why an element can be created, stored in a variable, passed around, and never rendered at all. ## jsx, jsxs, and jsxDEV `react/jsx-runtime` exports `jsx`, `jsxs`, and `Fragment`. The compiler emits `jsxs` when a tag has a statically known list of children: ```js _jsxs("ul", { children: [_jsx("li", {}, "a"), _jsx("li", {}, "b")] }); ``` The distinction exists so React can tell a compiler-generated static children array from an array a developer passed in, which needs key validation. The third argument of `jsx`/`jsxs` is the `key`, not a child. Development builds instead import `jsxDEV` from `react/jsx-dev-runtime`; it takes extra arguments carrying the source file name and line number, which is how React attaches useful locations to its warnings. ## Why interviewers ask this The desugaring explains a cluster of behaviours that otherwise look arbitrary: why component tags must be capitalized (a lowercase tag compiles to a string), why JSX attributes follow JavaScript identifier rules (`className`, `htmlFor`), why a tag must have exactly one root value per expression (a function call returns one value), why props spread merges left to right (it becomes an object literal with a spread), and why `{}` inside JSX takes any expression but not a statement (it becomes an argument position). It also grounds the mental model React itself relies on: producing elements is producing data. The render pass turns that data into work; nothing in the JSX expression itself touches the DOM. ## The one-sentence version "JSX compiles to `jsx(type, props, key)` calls that return plain objects describing the UI; React reads those objects and decides what to do with them." If you can then write out both the classic and automatic output on a whiteboard, you have answered the question completely.
- When does the compiler emit `jsxs` instead of `jsx`, and why do both exist?`jsxs` is emitted when a tag's children are a statically known list that the compiler itself built. Because React knows that array came from the compiler and not from user code, it can skip the key validation it applies to arrays a developer passes in. Both take `(type, props, key)`; only the children-array provenance differs.
- How do JSX attributes and a `{...rest}` spread end up in the props object?Each attribute becomes one key on the props object literal, and a spread is inlined into that literal in source order — so `<Input {...rest} type="text" />` compiles to props where `type` wins over anything in `rest`, while `<Input type="text" {...rest} />` lets `rest` override it. Order in the tag is order in the object.
- Does JSX have to produce React elements at all?No. The transform is configurable: the `jsxImportSource` option points it at any package that exports a `/jsx-runtime` module with `jsx`, `jsxs`, and `Fragment`. That is how libraries such as `@emotion/react` intercept element creation, and how non-React libraries reuse the same syntax.
A JSX element is like a filled-in order slip, not the meal: writing the slip is instant and describes what should exist, and the kitchen (React) is what actually cooks it.
saying these in an interview costs you the question
- Says JSX is HTML that the browser understands natively
- Claims a JSX expression evaluates to a DOM element
- Insists all JSX still compiles to React.createElement
- Thinks children are passed separately and never appear in props
- Cannot say what the compiled call returns