The ESLint no-invalid-jsx-nesting rule checks tags written directly inside
other tags. It catches markup the browser may repair before React hydrates it,
such as a <p> inside a <p>. hydration-proof's
no-invalid-interactive-nesting also follows fragments, conditions and lists
within a component. Neither rule sees through a separate component; use the
CLI to check the HTML the browser actually receives.
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 |
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.
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:
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 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).
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, 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:
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" },
},
];npm install -D eslint-plugin-hydration-proof eslint-plugin-validate-jsx-nestingSources
As of September 2026:
- The plugin's repository on GitHub, README and rule source (version 0.1.1)
- The package on npm: 41,962 downloads in the week of September 10–16, 2026; last published April 23, 2023
- eslint-plugin-react rules, no DOM nesting rule
no-invalid-interactive-nesting