Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions docs/react-types/CSSProperties.md
Original file line number Diff line number Diff line change
@@ -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<string | number>`, 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 (
<div style={{ backgroundColor: "papayawhip", padding: 16 }}>Hello</div>
);
}
```

### 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 <div style={card}>{children}</div>;
}
```

### 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 <div style={{ padding: 16, ...style }}>{children}</div>;
}
```

## Values: numbers vs strings

For length-like properties, **numbers are interpreted as pixels** — React appends `px` automatically. Strings are passed through verbatim.

```tsx
<div style={{ width: 100 }} /> // → width: 100px
<div style={{ width: "100%" }} /> // → width: 100%
<div style={{ width: "10rem" }} /> // → 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'.
<div style={{ "--accent": "tomato" }} />
```

There are three common workarounds.

### 1. Type assertion (quickest)

```tsx
<div style={{ "--accent": "tomato" } as CSSProperties} />
```

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",
};

<div style={style} />;
```

### 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 <color>
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`.
31 changes: 15 additions & 16 deletions docs/react-types/ComponentProps.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@
title: ComponentProps<ElementType>
---

`ComponentProps<ElementType>` constructs a type with all valid props of an element or inferred props of a component. It's an alias for `ComponentPropsWithRef<ElementType>`.
`ComponentProps<ElementType>` 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<T>` | The props as declared by the component or element. |
| `ComponentPropsWithRef<T>` | Same as `ComponentProps<T>`, plus `ref` for class components. For function components in React 19 the result is identical to `ComponentProps<T>` (since `ref` is already a regular prop). |
| `ComponentPropsWithoutRef<T>` | `ComponentProps<T>` 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<ElementType>` 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<T>` is usually all you need — `ref` is just a regular prop for function components. Reach for `ComponentPropsWithoutRef<T>` when you specifically need to remove `ref` from a spread.

**React ≤18**: Prefer `ComponentPropsWithRef<ElementType>` if ref is forwarded and `ComponentPropsWithoutRef<ElementType>` when ref is not forwarded.
**React ≤18:** Prefer `ComponentPropsWithRef<T>` when refs are forwarded, and `ComponentPropsWithoutRef<T>` when they are not.
:::

## Parameters
Expand All @@ -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)
}
```

Expand All @@ -49,28 +57,19 @@ type MyType = ComponentProps<typeof Component>;
// ^? 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 `<Icon>` 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 `<Icon>` 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";

type IconName = ComponentProps<typeof Icon>["name"];
// ^? type IconName = "warning" | "checkmark"
```

You can also use the `Pick<Type, Keys>` utility type to accomplish the same thing:

```tsx
import { Icon } from "component-library";

type IconName = Pick<ComponentProps<typeof Icon>, "name">;
// ^? type IconName = "warning" | "checkmark"
```
84 changes: 81 additions & 3 deletions docs/react-types/ReactNode.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<ReactNode>` (so arrays of nodes, but also any iterable)
- `ReactPortal`
- `Promise<ReactNode>` (for async Server Components — React unwraps the promise via Suspense)

## Parameters

Expand All @@ -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";
Expand All @@ -26,7 +37,7 @@ function Component({ children }: Props) {
}
```

`<Component>` accepts anything that React can render as `children`. Here are some examples:
`<Component>` accepts anything that React can render as `children`:

```tsx
function Examples() {
Expand All @@ -37,6 +48,7 @@ function Examples() {
</Component>
<Component>Hello</Component>
<Component>{123}</Component>
<Component>{42n}</Component>
<Component>
<>Hello</>
</Component>
Expand All @@ -48,3 +60,69 @@ function Examples() {
);
}
```

### Async Server Components

A Server Component can be `async` and return a `Promise<ReactNode>`. React unwraps the promise through the nearest `<Suspense>` boundary:

```tsx
// Server Component
async function UserProfile({ userId }: { userId: string }) {
const user = await fetchUser(userId);
return <p>{user.name}</p>;
}

function Page() {
return (
<Suspense fallback={<p>Loading…</p>}>
<UserProfile userId="42" />
</Suspense>
);
}
```

A bare `Promise<ReactNode>` 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<ReactNode>
return <Suspense fallback={<Loading />}>{userPromise}</Suspense>;
}
```

## `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<any, any>` — 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 };

<MyComponent content="hello" /> // ✅ string is a ReactNode
<MyComponent content={<span>hi</span>} /> // ✅ element is a ReactNode
<MyComponent content={null} /> // ✅ 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<ReactNode>`, 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 => <span>hello</span>;
```

[Something to add? File an issue](https://github.com/typescript-cheatsheets/react/issues/new).
Loading
Loading