jsx
Controls how JSX is emitted: preserved, React.createElement, or the automatic runtime.
"jsx": "preserve" | "react" | "react-native" | "react-jsx" | "react-jsxdev"- Added in TypeScript 1.6
Parameters
| Parameter | Type | Description |
|---|---|---|
$react-jsxoptional | string | Emits .js with JSX turned into _jsx calls imported from "<jsxImportSource>/jsx-runtime". The production form of the automatic runtime; no React import needed in source. |
$react-jsxdevoptional | string | Emits .js with _jsxDEV calls from "<jsxImportSource>/jsx-dev-runtime", passing the file name, line and column for better error messages. Development builds only. |
$preserveoptional | string | Emits .jsx with the JSX left untouched, for a downstream tool such as Babel, Vite or Next.js to transform. |
$reactoptional | string | The legacy classic runtime: emits .js with JSX rewritten to React.createElement calls. React must be in scope in every file. |
$react-nativeoptional | string | Emits .js with the JSX left unchanged, for the Metro bundler. |
$jsxImportSourceoptional | string= "react" | Companion option (TypeScript 4.1). The module specifier the automatic runtime imports from — "react" by default, or "preact", "@emotion/react", "solid-js" and so on. Applies to react-jsx and react-jsxdev only. |
Return value
Determines what JSX syntax is transformed into and which file extension is emitted.
Errors
JSX syntax in a file where jsx is unset is error TS17004. Under jsx "react", a file that uses JSX without React in scope is error TS2304 at compile time and "React is not defined" at runtime.
Changelog
- PHP 4.1Added react-jsx and react-jsxdev for React 17's automatic runtime, plus the jsxImportSource option and the per-file /** @jsxImportSource */ pragma.
- PHP 4.1Added the /* @jsxRuntime classic */ and /* @jsxRuntime automatic */ comments for overriding the runtime one file at a time.
jsx decides what happens to JSX syntax in .tsx files. Without it, JSX is a syntax error (TS17004); with it, TypeScript either rewrites the elements or hands them on untouched.
Classic versus automatic
The legacy value react compiles <h1>hi</h1> to React.createElement("h1", null, "hi"), which is why every file needed import React from "react" even when the name was never written. react-jsx uses the automatic runtime introduced in React 17: TypeScript inserts import { jsx as _jsx } from "react/jsx-runtime" itself, so the manual import disappears. react-jsxdev is the same transform with _jsxDEV and source positions attached, meant only for development builds.
When to choose preserve
If a bundler already handles JSX — Vite, Next.js, esbuild, Babel — set preserve. TypeScript then type checks the JSX and emits .jsx with the elements intact, letting the downstream tool own the transform. Two tools each transforming JSX is how you end up with double-wrapped output.
Beyond React
jsxImportSource retargets the automatic runtime at any library exposing jsx-runtime — Preact, Solid, Emotion. It can be overridden per file with a /** @jsxImportSource preact */ comment, provided the file is a module.
Example
// tsconfig.json
// { "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "preact" } }
// src/Badge.tsx — note: no React or Preact import in the source
export const Badge = ({ label }: { label: string }) => <span>{label}</span>;
// Emitted JavaScript:
// import { jsx as _jsx } from "preact/jsx-runtime";
// export const Badge = ({ label }) => _jsx("span", { children: label });
console.log(Badge.name, typeof Badge);
// -> Badge functionWhen to use it
- Dropping the boilerplate `import React from "react"` from every component file.
- Pointing the automatic runtime at Preact or Solid with jsxImportSource.
- Handing JSX untouched to Vite or Next.js while still type checking it.
- Opting one file into Emotion's runtime with a per-file @jsxImportSource pragma.
Frequently asked questions
What is the difference between jsx react and react-jsx?
react is the classic transform that emits React.createElement and requires React to be imported in every file, while react-jsx is React 17's automatic runtime that emits _jsx calls and inserts the import from react/jsx-runtime for you. Use react-jsx for new projects.
Why do I get "React refers to a UMD global" or "React is not defined"?
Because jsx is set to react, which compiles elements into React.createElement calls that need React in scope. Either add import React from "react" to the file or switch jsx to react-jsx so TypeScript emits the runtime import itself.
How do I use JSX with Preact or Solid instead of React?
Set "jsx": "react-jsx" and "jsxImportSource": "preact" (or "solid-js"), which makes TypeScript import the factory from preact/jsx-runtime. A single file can override it with a /** @jsxImportSource preact */ comment.
More examples
The same component under each value
Only react-jsx adds its own import; react relies on React being in scope, which is the source of the classic "React is not defined" runtime error. preserve is the only value that emits a .jsx file rather than .js.
// src/Hello.tsx
export const HelloWorld = () => <h1>Hello world</h1>;
// jsx: "react-jsx" -> Hello.js
// import { jsx as _jsx } from "react/jsx-runtime";
// export const HelloWorld = () => _jsx("h1", { children: "Hello world" });
// jsx: "react" -> Hello.js (React must already be imported)
// import React from "react";
// export const HelloWorld = () => React.createElement("h1", null, "Hello world");
// jsx: "preserve" -> Hello.jsx
// export const HelloWorld = () => <h1>Hello world</h1>;
console.log("HelloWorld is a", typeof HelloWorld);
// -> HelloWorld is a functionPer-file overrides for a mixed codebase
The pragma lets one file use Emotion's runtime, which is what makes the non-standard css prop type check, while the rest of the project keeps React's. A sibling comment, /* @jsxRuntime classic */, switches a single file back to React.createElement.
/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";
// This one file compiles to imports from "@emotion/react/jsx-runtime",
// even though tsconfig says jsxImportSource: "react".
// The file must contain an import or export to count as a module.
const warning = css({ color: "crimson" });
export const Warning = ({ text }: { text: string }) => (
<p css={warning}>{text}</p>
);
console.log(typeof Warning, Object.keys({ warning }).length);
// -> function 1
Discussion