# eslint-plugin-validate-jsx-nesting vs hydration-proof

> The ESLint no-invalid-jsx-nesting rule checks direct parent and child tags. What it catches, what it misses, and how hydration-proof's nesting rule differs.

Source: https://hydration.jscrate.dev/docs/compare/eslint-plugin-validate-jsx-nesting
Last updated: 2026-09-18

The ESLint no-invalid-jsx-nesting rule, from eslint-plugin-validate-jsx-nesting, reports HTML nesting the browser would repair, such as a `<p>` inside a `<p>`, when the child tag is written directly inside the parent. hydration-proof's `no-invalid-interactive-nesting` looks through fragments, conditions and lists and at any depth, and explains the hydration error each case causes.

## At a glance

|                                                                         | `no-invalid-jsx-nesting`                                        | `no-invalid-interactive-nesting`                                 |
| ----------------------------------------------------------------------- | --------------------------------------------------------------- | ---------------------------------------------------------------- |
| Checks                                                                  | A tag and its direct parent tag                                 | A tag and every enclosing tag in the same component              |
| Through fragments, `&&`, `? :`, arrays, `.map()`                        | No                                                              | Yes                                                              |
| `<div>` inside `<span>` inside `<p>`                                    | Not reported                                                    | Reported                                                         |
| `<a>` in `<a>`, `<button>` in `<button>`, `<form>` in `<form>`          | Direct children                                                 | At any depth                                                     |
| Interactive content in `<a>` or `<button>`                              | Not checked                                                     | `input`, `select`, `textarea`, `label`, `iframe` and more        |
| Table structure                                                         | `<tr>` outside a table section, `<td>` outside `<tr>`, and more | `<tr>` directly in `<table>`, `<td>` directly in a table section |
| Headings in headings, `<li>` in `<li>`, `<select>` and `<head>` content | Checked                                                         | Not checked                                                      |
| Across components                                                       | No                                                              | No; `hydration-proof test` finds those                           |
| Server Components                                                       | Checked                                                         | Checked, even with the `next` preset                             |
| Message                                                                 | `Invalid HTML nesting: <div> can not be child of <p>`           | What the parser does and how hydration fails                     |
| Latest release                                                          | 0.1.1, April 2023                                               | 1.0                                                              |

eslint-plugin-react, the other common React linter, has no rule for DOM nesting.

## What ESLint no-invalid-jsx-nesting does well

The plugin has one rule and does it simply:

- **A broad list of content rules.** It uses the validate-html-nesting library, which knows the only valid children of `<table>`, `<tr>`, `<select>`, `<head>` and `<colgroup>`, elements such as `<textarea>` and `<option>` that take no element children, and pairs like a heading in a heading or `<li>` in `<li>`. Some of these are outside what hydration-proof's rule checks.
- **Framework agnostic.** It works on any JSX, not only React.
- **Small.** Its only dependency is validate-html-nesting.

It has about 42,000 weekly downloads on npm.

## Where hydration-proof differs

**It looks past the direct parent.** `no-invalid-jsx-nesting` compares a tag with its parent only when both are HTML tags written next to each other. These all break hydration and are not reported by it:

```tsx title="components/intro.tsx"
export function Intro({
  items,
  showMore,
}: {
  items: string[];
  showMore: boolean;
}) {
  return (
    <p>
      Welcome.
      {/* Inside a condition: the parent of <div> is not the <p> tag. */}
      {showMore && <div>Read more below.</div>}
      {/* One level deeper: the browser still closes the <p>. */}
      <span>
        <ul>
          {items.map((item) => (
            <li key={item}>{item}</li>
          ))}
        </ul>
      </span>
    </p>
  );
}
```

`no-invalid-interactive-nesting` follows fragments, `&&`, `? :`, arrays and `.map()` callbacks, and walks every enclosing tag in the component, stopping where the HTML parser would. It reports both cases above.

**It covers interactive content.** A `<button>`, `<input>` or `<select>` inside a link, or a link inside a button, is reported with the reason. See [HP3002](https://hydration.jscrate.dev/docs/issues/hp3002) for what the CLI reports.

**It explains the hydration error.** Each message says what the browser does with the markup and how that breaks hydration, for example that the browser closes the `<p>` before the `<div>`, so the DOM no longer matches what React rendered.

**It is part of a hydration rule set.** The same plugin has 14 more rules for the clock, random values, locale, timezone, storage and browser APIs ([all rules](https://hydration.jscrate.dev/docs/rules)).

Neither linter sees nesting across components: `<p><Card /></p>` where `Card` renders a `<div>`. `hydration-proof test` checks the real server HTML with React's own nesting rules and reports it as [HP3001](https://hydration.jscrate.dev/docs/issues/hp3001), with the line in the server HTML.

## Use both

They overlap on the common cases, like a `<div>` written directly inside a `<p>`, which then gets two reports. Each also catches something the other does not, so running both is reasonable:

```js title="eslint.config.js"
import hydrationProof from "eslint-plugin-hydration-proof";
import validateJsxNesting from "eslint-plugin-validate-jsx-nesting";

export default [
  hydrationProof.configs.recommended,
  {
    plugins: { "validate-jsx-nesting": validateJsxNesting },
    rules: { "validate-jsx-nesting/no-invalid-jsx-nesting": "error" },
  },
];
```

```bash
npm install -D eslint-plugin-hydration-proof eslint-plugin-validate-jsx-nesting
```

## Sources

As of September 2026:

- [The plugin's repository on GitHub](https://github.com/MananTank/eslint-plugin-validate-jsx-nesting), README and rule source (version 0.1.1)
- [The package on npm](https://www.npmjs.com/package/eslint-plugin-validate-jsx-nesting): 41,962 downloads in the week of September 10–16, 2026; last published April 23, 2023
- [eslint-plugin-react rules](https://github.com/jsx-eslint/eslint-plugin-react#list-of-supported-rules), no DOM nesting rule
- [`no-invalid-interactive-nesting`](https://hydration.jscrate.dev/docs/rules/no-invalid-interactive-nesting)

## Related

- [no-invalid-interactive-nesting](https://hydration.jscrate.dev/docs/rules/no-invalid-interactive-nesting)
- [div cannot be a descendant of p](https://hydration.jscrate.dev/docs/errors/div-cannot-be-a-descendant-of-p)
- [Invalid HTML nesting and hydration](https://hydration.jscrate.dev/docs/causes/invalid-html)
- [validateDOMNesting warnings](https://hydration.jscrate.dev/docs/errors/validatedomnesting)
- [eslint-plugin-react-hooks purity compared](https://hydration.jscrate.dev/docs/compare/eslint-plugin-react-hooks)
