# In HTML, div cannot be a descendant of p

> In HTML, div cannot be a descendant of p: the browser closes the paragraph before the div, so hydration fails. Why, and how to fix it in your code or MUI.

Source: https://hydration.jscrate.dev/docs/errors/div-cannot-be-a-descendant-of-p
Last updated: 2026-09-18

In HTML, `<div>` cannot be a descendant of `<p>`: a paragraph may only
contain text-level elements. When the server sends a `<div>` inside a `<p>`,
the browser closes the paragraph before the `<div>`, so the DOM no longer
matches what React rendered and hydration fails. Use a `<span>` inside, or
make the outer element a `<div>`.

## The error

React 19 logs this in development:

```text
In HTML, <div> cannot be a descendant of <p>.
This will cause a hydration error.
```

Under it, React prints the component tree from the `<p>` down to the `<div>`,
with both lines marked `>`. When the two elements are in different components,
it logs a second message, `<p> cannot contain a nested <div>.`, whose stack
points at the component that renders the `<p>`.

React 18 logs it as a `validateDOMNesting` warning with a component stack:

```text
Warning: validateDOMNesting(...): <div> cannot appear as a descendant of <p>.
    at div
    at p
    at Card (webpack-internal:///./components/card.tsx:9:11)
```

The same message appears for every block element inside a paragraph:

```text
In HTML, <p> cannot be a descendant of <p>.
In HTML, <ul> cannot be a descendant of <p>.
In HTML, <ol> cannot be a descendant of <p>.
In HTML, <pre> cannot be a descendant of <p>.
In HTML, <table> cannot be a descendant of <p>.
In HTML, <h2> cannot be a descendant of <p>.
```

These are development warnings, with no production form. What you get in
production is the hydration error that follows: #418.

## Why div cannot be a descendant of p

A `<p>` element can only hold phrasing content: text, `<span>`, `<a>`,
`<strong>`, `<img>` and similar. When the HTML parser meets the start of a
block element, such as `<div>`, `<p>`, `<ul>`, `<ol>`, `<pre>`, `<table>` or a
heading, while a paragraph is open, it closes the paragraph first. The `</p>`
that comes later has nothing to close, so the parser adds an empty paragraph:

```text
server HTML:  <p>Intro<div>Details</div></p>
browser DOM:  <p>Intro</p><div>Details</div><p></p>
```

React then hydrates this DOM, expects the `<div>` inside the `<p>`, and does
not find it. React 19 throws
["Hydration failed because the server rendered HTML didn't match the client"](https://hydration.jscrate.dev/docs/errors/hydration-failed-server-rendered-html-didnt-match-client),
and React 18 throws
["Hydration failed because the initial UI does not match…"](https://hydration.jscrate.dev/docs/errors/hydration-failed-initial-ui-does-not-match).
Either way the page is rendered again in the browser.

Pages rendered only in the browser do not break, because DOM methods do not
repair nesting the way the parser does. That is why the warning says it "will
cause a hydration error": server rendering is what exposes it.

## Common causes

- **A `<div>` or a list written inside a `<p>`** in your own JSX.
- **A component that renders a block element, used inside a paragraph.**
  `<p><Card /></p>` looks fine until `Card` renders a `<div>`.
- **Component libraries whose text component is a `<p>`.** MUI's
  `<Typography>` renders a `<p>` for body text by default, and Chakra's
  `<Text>` does too. A `<Box>` (a `<div>`) or another `<Typography>` inside it
  is a `<div>` or `<p>` in a `<p>`.
- **Markdown renderers.** They wrap images in a `<p>`. A custom image
  component that renders a `<figure>` or a `<div>` ends up inside it.
- **HTML from a CMS** injected with `dangerouslySetInnerHTML` into a `<p>`.
  React cannot warn about it, but the browser splits it the same way.

See [invalid HTML nesting](https://hydration.jscrate.dev/docs/causes/invalid-html) for the other rules the
parser enforces.

## How to fix it

1. **Find the paragraph.** In React 18, the `validateDOMNesting(...): <div> cannot appear as a descendant of <p>` warning names no file in the error itself, so read its component stack from the top: the first component listed renders the `<div>`, and the first one after `at p` renders the paragraph.
2. **Change one of the two elements.** Use a `<span>` (styled as a block if
   needed) inside, or a `<div>` outside. Before:

   ```tsx title="components/card.tsx"
   import type { ReactNode } from "react";

   export function Card({
     title,
     children,
   }: {
     title: string;
     children: ReactNode;
   }) {
     return (
       <p className="card">
         <strong>{title}</strong>
         <div className="card-body">{children}</div>
       </p>
     );
   }
   ```

   After:

   ```tsx title="components/card.tsx"
   import type { ReactNode } from "react";

   export function Card({
     title,
     children,
   }: {
     title: string;
     children: ReactNode;
   }) {
     return (
       <div className="card">
         <p>
           <strong>{title}</strong>
         </p>
         <div className="card-body">{children}</div>
       </div>
     );
   }
   ```

3. **In MUI, change what `Typography` renders.** Its `component` prop picks
   the element:

   ```tsx title="components/total.tsx"
   import Box from "@mui/material/Box";
   import Typography from "@mui/material/Typography";

   export function Total() {
     return (
       <Typography component="div">
         <Box sx={{ display: "flex", gap: 1 }}>
           <span>Total</span>
           <strong>$42</strong>
         </Box>
       </Typography>
     );
   }
   ```

4. **In Markdown renderers,** render images with inline elements only, or
   render paragraphs as `<div>`.

## Find every instance

A linter only sees nesting inside one component. hydration-proof reads the
real server HTML, so it also finds `<p><Card /></p>` across components and
HTML from a CMS. It reports each case as [HP3001](https://hydration.jscrate.dev/docs/issues/hp3001), with
the line in the server HTML:

```bash
npx hydration-proof test
```

[`no-invalid-interactive-nesting`](https://hydration.jscrate.dev/docs/rules/no-invalid-interactive-nesting)
reports every element that closes an open `<p>`, including in Server
Components, while you type.

## Related

- [validateDOMNesting: tables, links and buttons](https://hydration.jscrate.dev/docs/errors/validatedomnesting)
- [Invalid HTML nesting and hydration errors](https://hydration.jscrate.dev/docs/causes/invalid-html)
- [HP3001: invalid HTML nesting](https://hydration.jscrate.dev/docs/issues/hp3001)
- [The no-invalid-interactive-nesting rule](https://hydration.jscrate.dev/docs/rules/no-invalid-interactive-nesting)
- [eslint-plugin-validate-jsx-nesting compared](https://hydration.jscrate.dev/docs/compare/eslint-plugin-validate-jsx-nesting)
