JSX and Frameworks
|
This section documents the current TypeScript release line as published at the official TypeScript documentation, which is the reference these pages are written and verified against. No specific patch version is pinned. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, since TypeScript iterates quickly. This section’s bibliography lists the reference material consulted while preparing these pages. |
JSX is an expression syntax for element trees. TypeScript parses and type-checks it natively when a file
uses the .tsx extension. This page covers only the language side — the compiler options and the JSX.*
types — and hands off typing a component’s props, events and refs to the framework pages. The official
JSX handbook chapter is the companion reference.
The .tsx extension and the jsx option
A file containing JSX must use .tsx; a plain .ts file rejects angle-bracket elements. The jsx
compiler option (see tsconfig.json and Compiler Options) chooses what <div/>
compiles to:
-
react-jsx— automatic runtime: callsjsx/jsxsfromreact/jsx-runtime, noimport React. The usual production setting. -
react-jsxdev— same automatic runtime, development build with extra debugging fields. -
preserve— leaves JSX in place, emits.jsxfor a downstream tool (Babel, esbuild) to transform. -
react— classic runtime: rewrites elements toReact.createElement, soReactmust be in scope. -
react-native— likepreserve, but emits a.jsfile.
{
"compilerOptions": {
"jsx": "react-jsx",
"lib": ["ES2022", "DOM"]
}
}
// greeting.tsx
export function Greeting({ name }: { name: string }) {
return <p className="greeting">Hello, {name}</p>;
}
as casts are ambiguous in .tsx
The angle-bracket cast form <Foo>value cannot be used in a .tsx file — <Foo> reads as an unclosed
JSX tag. Use value as Foo, or prefer value satisfies Foo when you only want the shape checked without
widening. When the expression spans several lines, put the as on its own line so it stays readable.
// <string>input; // Error in .tsx: parsed as a JSX element
const text = input as string; // ok
const config = {
retries: 3,
timeout: 5_000,
} satisfies RequestConfig; // verifies the shape, keeps the literal types
const root = (
document.getElementById("app")
) as HTMLDivElement; // 'as' on its own line for a multi-line expression
JSX.Element vs. React.ReactNode vs. React.ReactElement
-
React.ReactElement— a created element object, roughly\{ type, props, key }; takes type parameters for its props and type, so it is the most precise of the three. -
JSX.Element— a global alias resolving toReactElement. Represents a single JSX expression; cannot stand for a string, number, array ornull. -
React.ReactNode— anything React can render: an element, string, number, boolean,null,undefined, or an array of those. The right type for achildrenprop and for a component’s return value. -
JSX.IntrinsicElements— global interface mapping built-in tag names to their prop types. Index it to reuse a host element’s props, e.g.JSX.IntrinsicElements['button'].
import type { ReactNode, ReactElement } from "react";
const a: JSX.Element = <span />; // exactly one element
const b: ReactElement = <span />; // same object, framework-neutral name
const c: ReactNode = ["hi", 42, <span />, null]; // anything renderable
// Reuse the intrinsic <button> props and add one of your own:
type ButtonProps = JSX.IntrinsicElements["button"] & { loading?: boolean };
function Panel({ children }: { children: ReactNode }): ReactNode {
return <section>{children}</section>;
}
The jsxImportSource pragma and tsconfig option
With jsx: "react-jsx", the automatic runtime is imported from react/jsx-runtime. The
jsxImportSource tsconfig option changes that module prefix project-wide, and a per-file
/** @jsxImportSource <pkg> */ comment pragma overrides it for one file — useful for Preact, Emotion or
solid-js.
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact"
}
}
/** @jsxImportSource @emotion/react */
// JSX in this file now calls @emotion/react/jsx-runtime, overriding tsconfig
export const Box = () => <div css={{ padding: 8 }} />;
Typing components — briefly, then defer
Props are just an object type on the first parameter, children is ReactNode, DOM events use React’s
synthetic event types, refs use useRef<T>, and a generic component is a generic function (see
Generics). Keep annotations minimal and let inference do the rest.
import { useRef, type ChangeEvent, type ReactNode } from "react";
type FieldProps<T extends string> = {
name: T;
label: ReactNode; // children-style content
onChange: (name: T, value: string) => void; // typed callback prop
};
// A generic component is just a generic function:
function Field<T extends string>({ name, label, onChange }: FieldProps<T>) {
const ref = useRef<HTMLInputElement>(null); // typed ref
const handle = (e: ChangeEvent<HTMLInputElement>) => onChange(name, e.target.value);
return <label>{label}<input ref={ref} onChange={handle} /></label>;
}
This page stops here on purpose. React’s own TypeScript guide is the
practical companion; for discriminated props, forwardRef typing, generic components, hooks, context and
the React.FC trade-offs see TypeScript with React, and for Angular’s
template type-checking, typed @Input()/@Output() and signals see
TypeScript Essentials for Angular.
See also
-
tsconfig.json and Compiler Options — the
jsxandjsxImportSourceoptions in context. -
Generics — a generic component is just a generic function.
-
TypeScript with React — the full component-typing surface.
-
TypeScript Essentials for Angular — typing the Angular side.