diff --git a/docs/react-types/CSSProperties.md b/docs/react-types/CSSProperties.md
new file mode 100644
index 000000000..94ad90209
--- /dev/null
+++ b/docs/react-types/CSSProperties.md
@@ -0,0 +1,150 @@
+---
+title: CSSProperties
+---
+
+`CSSProperties` is the type for inline styles passed via the `style` prop. It extends [`csstype`](https://github.com/frenic/csstype)'s `Properties`, so every standard CSS property is covered with autocompletion and value validation.
+
+## Parameters
+
+`CSSProperties` does not take any parameters.
+
+## Usage
+
+### Inline styles
+
+```tsx
+function Banner() {
+ return (
+
Hello
+ );
+}
+```
+
+### Reusable style objects
+
+Pull style objects out to share between elements. Annotate with `CSSProperties` so TypeScript checks the values:
+
+```tsx
+import { CSSProperties } from "react";
+
+const card: CSSProperties = {
+ borderRadius: 8,
+ padding: 16,
+ boxShadow: "0 1px 3px rgba(0, 0, 0, 0.1)",
+};
+
+function Card({ children }: { children: ReactNode }) {
+ return
{children}
;
+}
+```
+
+### Typing a `style` prop on your own component
+
+If your component forwards a style object, type the prop as `CSSProperties` directly — that's exactly what HTML elements use:
+
+```tsx
+type BoxProps = {
+ style?: CSSProperties;
+ children?: ReactNode;
+};
+
+function Box({ style, children }: BoxProps) {
+ return
{children}
;
+}
+```
+
+## Values: numbers vs strings
+
+For length-like properties, **numbers are interpreted as pixels** — React appends `px` automatically. Strings are passed through verbatim.
+
+```tsx
+ // → width: 100px
+ // → width: 100%
+ // → width: 10rem
+```
+
+A handful of properties (such as `lineHeight`, `opacity`, `zIndex`, `flexGrow`) are unitless — passing a number leaves it unitless.
+
+## Vendor prefixes
+
+Vendor-prefixed properties are written in PascalCase, not with the leading hyphen:
+
+```tsx
+const style: CSSProperties = {
+ WebkitTransform: "rotate(45deg)",
+ MozAppearance: "none",
+};
+```
+
+## Custom properties (CSS variables)
+
+`CSSProperties` deliberately has no index signature, so writing a CSS custom property triggers a TypeScript error:
+
+```tsx
+// ❌ Type '{ "--accent": string; }' is not assignable to type 'CSSProperties'.
+
+```
+
+There are three common workarounds.
+
+### 1. Type assertion (quickest)
+
+```tsx
+
+```
+
+Fine for one-off use, but you lose type-checking on the rest of the object.
+
+### 2. Intersection with an indexed type (recommended)
+
+Keep type-checking for normal properties while allowing any `--*` key:
+
+```tsx
+type CSSPropertiesWithVars = CSSProperties & {
+ [key: `--${string}`]: string | number;
+};
+
+const style: CSSPropertiesWithVars = {
+ color: "white",
+ "--accent": "tomato",
+};
+
+;
+```
+
+### 3. Module augmentation (when you have a fixed set of variables)
+
+If your design system has a known list of CSS variables, augment `CSSProperties` once and get autocomplete everywhere:
+
+```tsx
+// global.d.ts
+import "react";
+
+declare module "react" {
+ interface CSSProperties {
+ "--accent"?: string;
+ "--spacing"?: string | number;
+ }
+}
+```
+
+After this, `style={{ "--accent": "tomato" }}` type-checks with no assertion.
+
+## Typing individual CSS values with `csstype`
+
+If you want a prop that accepts a single CSS value — e.g. a color or a display value — import the underlying [`csstype`](https://github.com/frenic/csstype) package and use the `Property` namespace:
+
+```tsx
+import type { Property } from "csstype";
+
+type BadgeProps = {
+ color?: Property.Color; // any valid CSS
+ display?: Property.Display;
+};
+```
+
+`csstype` is already a transitive dependency of `@types/react`, so no extra install is needed — just import the types.
+
+## CSS-in-JS libraries
+
+Most CSS-in-JS libraries (Emotion, styled-components, Stitches, vanilla-extract, …) augment `CSSProperties` themselves to support library-specific features such as nested selectors, pseudo-classes as object keys, or theme tokens. If you see properties like `"&:hover"` accepted as keys, that's a library augmentation, not a feature of `@types/react`.
diff --git a/docs/react-types/ComponentProps.md b/docs/react-types/ComponentProps.md
index 13c67ac9f..714f59ca5 100644
--- a/docs/react-types/ComponentProps.md
+++ b/docs/react-types/ComponentProps.md
@@ -2,12 +2,20 @@
title: ComponentProps
---
-`ComponentProps` constructs a type with all valid props of an element or inferred props of a component. It's an alias for `ComponentPropsWithRef`.
+`ComponentProps` constructs a type with all valid props of an element or inferred props of a component.
+
+`@types/react` ships three related utilities:
+
+| Type | What it gives you |
+| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `ComponentProps` | The props as declared by the component or element. |
+| `ComponentPropsWithRef` | Same as `ComponentProps`, plus `ref` for class components. For function components in React 19 the result is identical to `ComponentProps` (since `ref` is already a regular prop). |
+| `ComponentPropsWithoutRef` | `ComponentProps` with any `ref` prop stripped out. Useful when you spread props onto a child element and don't want `ref` to leak. |
:::note
-**React 19+**: `ComponentPropsWithRef` is recommended as refs are now passed as props in function components. (See [forwardRef/createRef](/docs/basic/getting-started/forward_and_create_ref))
+**React 19+:** `ComponentProps` is usually all you need — `ref` is just a regular prop for function components. Reach for `ComponentPropsWithoutRef` when you specifically need to remove `ref` from a spread.
-**React ≤18**: Prefer `ComponentPropsWithRef` if ref is forwarded and `ComponentPropsWithoutRef` when ref is not forwarded.
+**React ≤18:** Prefer `ComponentPropsWithRef` when refs are forwarded, and `ComponentPropsWithoutRef` when they are not.
:::
## Parameters
@@ -28,7 +36,7 @@ interface Props extends ComponentProps<"div"> {
}
function Component({ className, children, text, ...props }: Props) {
- // `props` includes `text` in addition to all valid `div` props
+ // `props` includes all valid `div` props (minus the ones destructured above)
}
```
@@ -49,15 +57,15 @@ type MyType = ComponentProps;
// ^? type MyType = Props
```
-#### Infer specific prop type
+#### Infer a specific prop type
-The type of a specific prop can also be inferred this way. Let's say you are using an `` component from a component library. The component takes a `name` prop that determines what icon is shown. You need to use the type of `name` in your app, but it's not made available by the library. You could create a custom type:
+The type of a specific prop can also be inferred. Let's say you are using an `` component from a component library. The component takes a `name` prop that determines what icon is shown. You need to use the type of `name` in your app, but it's not made available by the library. You could create a custom type:
```tsx
type IconName = "warning" | "checkmark";
```
-However, this type is not really reflecting the actual set of icons made available by the library. A better solution is to infer the type:
+However, this type doesn't reflect the actual set of icons the library exposes. A better solution is to infer the type by indexing into the inferred props:
```tsx
import { Icon } from "component-library";
@@ -65,12 +73,3 @@ import { Icon } from "component-library";
type IconName = ComponentProps["name"];
// ^? type IconName = "warning" | "checkmark"
```
-
-You can also use the `Pick` utility type to accomplish the same thing:
-
-```tsx
-import { Icon } from "component-library";
-
-type IconName = Pick, "name">;
-// ^? type IconName = "warning" | "checkmark"
-```
diff --git a/docs/react-types/ReactNode.md b/docs/react-types/ReactNode.md
index fe8954f99..960c427cf 100644
--- a/docs/react-types/ReactNode.md
+++ b/docs/react-types/ReactNode.md
@@ -2,7 +2,18 @@
title: ReactNode
---
-`ReactNode` is a type that describes what React can render.
+`ReactNode` is a type that describes what React can render. It's a union of every value React accepts as a child:
+
+- `ReactElement` (the result of JSX, `createElement`, or `cloneElement`)
+- `string`
+- `number`
+- `bigint`
+- `boolean` (`true` and `false` render as nothing)
+- `null`
+- `undefined`
+- `Iterable` (so arrays of nodes, but also any iterable)
+- `ReactPortal`
+- `Promise` (for async Server Components — React unwraps the promise via Suspense)
## Parameters
@@ -12,7 +23,7 @@ title: ReactNode
### Typing `children`
-The most common use case for `ReactNode` is typing `children`.
+The most common use case for `ReactNode` is typing `children`.
```tsx
import { ReactNode } from "react";
@@ -26,7 +37,7 @@ function Component({ children }: Props) {
}
```
-`` accepts anything that React can render as `children`. Here are some examples:
+`` accepts anything that React can render as `children`:
```tsx
function Examples() {
@@ -37,6 +48,7 @@ function Examples() {
Hello{123}
+ {42n}
<>Hello>
@@ -48,3 +60,69 @@ function Examples() {
);
}
```
+
+### Async Server Components
+
+A Server Component can be `async` and return a `Promise`. React unwraps the promise through the nearest `` boundary:
+
+```tsx
+// Server Component
+async function UserProfile({ userId }: { userId: string }) {
+ const user = await fetchUser(userId);
+ return
{user.name}
;
+}
+
+function Page() {
+ return (
+ Loading…
}>
+
+
+ );
+}
+```
+
+A bare `Promise` can also be passed as children directly — useful for streaming patterns where the parent kicks off the work and the child resolves it:
+
+```tsx
+function Page() {
+ const userPromise = fetchUser(); // Promise
+ return }>{userPromise};
+}
+```
+
+## `ReactNode` vs `ReactElement` vs `JSX.Element`
+
+These three types are often confused because all three appear when you write JSX. They are not interchangeable:
+
+- **`ReactNode`** is the broadest: anything React can render, including primitives, `null`, arrays, and elements.
+- **`ReactElement`** describes only the object produced by JSX or `createElement` — it has `type`, `props`, and `key`. A `string` is _not_ a `ReactElement`.
+- **`React.JSX.Element`** is essentially `ReactElement` — what the JSX transform infers for a JSX expression.
+
+### Use `ReactNode` for `children`
+
+`ReactNode` is the correct type for any prop that receives children-like content, because a caller might pass a string, an array, or `null`:
+
+```tsx
+type Props = { content: ReactNode };
+
+ // ✅ string is a ReactNode
+hi} /> // ✅ element is a ReactNode
+ // ✅ null is a ReactNode
+```
+
+### Don't use `ReactNode` as a function-component return type
+
+A function component's return type should be what React allows components to _return_, not what it allows them to _receive_. Returning a plain `ReactNode` (which includes `bigint`, `Promise`, etc.) is broader than what TypeScript wants to see from a JSX-rendered component. Let TypeScript infer the return type, or use `React.JSX.Element` / `ReactElement` if you must annotate:
+
+```tsx
+// 👎 too broad, and historically caused issues when used in JSX
+const MyComponent = (): ReactNode => "hello";
+
+// 👍 let TS infer
+const MyComponent = () => "hello";
+
+// 👍 explicit
+const MyComponent = (): React.JSX.Element => hello;
+```
+
+[Something to add? File an issue](https://github.com/typescript-cheatsheets/react/issues/new).
diff --git a/docs/react-types/Ref.md b/docs/react-types/Ref.md
new file mode 100644
index 000000000..ea9b2968a
--- /dev/null
+++ b/docs/react-types/Ref.md
@@ -0,0 +1,120 @@
+---
+title: Ref, RefObject, RefCallback
+---
+
+`@types/react` ships three closely related ref types. Understanding how they fit together is the key to typing refs correctly in React 19, where `ref` is a regular prop on function components.
+
+| Type | What it is | When to use |
+| ---------------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
+| `RefObject` | An object with a `current: T` field. | Return type of `useRef` and `createRef`. Pass it as `ref={…}` to read/write `.current`. |
+| `RefCallback` | A function that receives the instance (or `null` on unmount). | Inline `ref={node => …}` callbacks. May return a cleanup function in React 19+. |
+| `Ref` | `RefCallback \| RefObject \| null`. | Use this as the **prop type** when accepting a ref from a parent — the parent might pass either form. |
+
+```ts
+interface RefObject {
+ current: T;
+}
+
+type RefCallback = (instance: T | null) => void | (() => void);
+
+type Ref = RefCallback | RefObject | null;
+```
+
+## `RefObject`
+
+`RefObject` is what `useRef` and `createRef` return. Its `.current` is typed based on the initial value you pass:
+
+```tsx
+import { useRef } from "react";
+
+const inputRef = useRef(null);
+// ^? RefObject
+
+const idRef = useRef(0);
+// ^? RefObject
+```
+
+When you pass `null` as the initial value with an explicit generic, React manages `.current` for you — TypeScript types it as `T | null` so you have to null-check before use:
+
+```tsx
+useEffect(() => {
+ inputRef.current?.focus();
+}, []);
+```
+
+> `MutableRefObject` still exists in `@types/react` for backwards compatibility but is `@deprecated` — use `RefObject` instead.
+
+## `RefCallback`
+
+Callback refs are useful when you need to run code the moment the DOM node is attached or detached. The callback is called with the node when it mounts, and with `null` when it unmounts:
+
+```tsx
+
{
+ if (node) console.log("mounted", node);
+ else console.log("unmounted");
+ }}
+/>
+```
+
+### Cleanup function (React 19)
+
+In React 19, a ref callback can return a cleanup function — React calls it instead of invoking the callback again with `null`. This makes ref callbacks symmetric with `useEffect`:
+
+```tsx
+
{
+ const observer = new IntersectionObserver(/* ... */);
+ observer.observe(node);
+ return () => observer.disconnect();
+ }}
+/>
+```
+
+If your callback returns nothing, React falls back to the old behavior and calls it with `null` on unmount.
+
+## `Ref` (the union)
+
+`Ref` is the type you should use when **accepting** a ref as a prop, because a caller can pass either a `RefObject` or a callback.
+
+```tsx
+import { Ref } from "react";
+
+type FancyInputProps = {
+ ref?: Ref;
+ placeholder?: string;
+};
+
+function FancyInput({ ref, placeholder }: FancyInputProps) {
+ return ;
+}
+```
+
+In React 19 this is all you need — `ref` is a regular prop, no `forwardRef` wrapper required.
+
+### Forwarding a ref to a different element
+
+If the ref you accept doesn't belong on the root element, you can still pass it down — `Ref` is assignable to any element's `ref` prop as long as `T` matches:
+
+```tsx
+type LabelledInputProps = {
+ label: string;
+ ref?: Ref;
+};
+
+function LabelledInput({ label, ref }: LabelledInputProps) {
+ return (
+
+ );
+}
+```
+
+## Related types
+
+- **`ForwardedRef`** — the legacy `ref` parameter type passed to a `forwardRef` render function. Only relevant if you still use `forwardRef`; prefer `Ref` on a prop instead.
+- **`LegacyRef`** — `@deprecated` alias for `Ref`. String refs are no longer supported.
+- **`ComponentRef`** — the ref type accepted by a given component or element, e.g. `ComponentRef<"input">` is `HTMLInputElement`. Useful when you want the ref type without writing it out by hand.
+- **`RefAttributes`** — the prop shape `{ ref?: Ref }`. Rarely needed directly; intersected by `ComponentPropsWithRef`.
diff --git a/docs/react-types/index.md b/docs/react-types/index.md
index 55e76caea..4d86ede51 100644
--- a/docs/react-types/index.md
+++ b/docs/react-types/index.md
@@ -5,4 +5,6 @@ title: React Types
`@types/react` makes some types available that can be very useful. Here's a list in alphabetical order with links to the detailed reference pages.
- [`ComponentProps`](/docs/react-types/ComponentProps)
+- [`CSSProperties`](/docs/react-types/CSSProperties)
- [`ReactNode`](/docs/react-types/ReactNode)
+- [`Ref`, `RefObject`, `RefCallback`](/docs/react-types/Ref)