bun/jsx-no-forbidden-nesting

Disallow a JSX element inside another element that cannot contain it

HTML doesn't allow a link or a button inside another link or button. Browsers disagree about what a click on the inner element does. In server-rendered HTML, the parser ends an a or a button where the next one starts and ignores a form inside a form, so the DOM doesn't match your JSX.

The inner element is reported at any depth, not only as a direct child. By default the rule knows these pairs:

OuterCannot contain
aa, button
buttona, button
formform
labellabel

Only the tag names are compared, so a Link component that renders an a doesn't count as an a.

Configure#

Off by default. To turn it on:

bunfig.toml
[lint.rules]
"bun/jsx-no-forbidden-nesting" = "error" # or "warn"

Every option, set to its default. The ones that are commented out are unset by default.

bunfig.toml
[lint.rules]
"bun/jsx-no-forbidden-nesting" = ["error", {
  # pairs = <list of objects>,
}]

Examples#

example.jsx1 problem
const card = (
  <a href={post.url}>
    <h2>{post.title}</h2>
    <button onClick={save}>Save</button>
  </a>
);
example.jsxNo problems
const card = (
  <article>
    <a href={post.url}>
      <h2>{post.title}</h2>
    </a>
    <button onClick={save}>Save</button>
  </article>
);

Options#

OptionDefaultWhat it does
pairsThe table aboveA list of { outer, inner } names. It replaces the default list.

Use it for your own components:

bunfig.toml
[lint.rules]
"bun/jsx-no-forbidden-nesting" = ["error", {
  pairs = [{ outer = "Link", inner = "Button" }],
}]
example.jsxwith { "pairs": [{ "outer": "Link", "inner": "Button" }] }1 problem
const row = (
  <Link to="/settings">
    <Button>Settings</Button>
  </Link>
);

Then the default pairs no longer apply. Add them to the list if you want to keep them:

example.jsxwith { "pairs": [{ "outer": "Link", "inner": "Button" }] }No problems
const row = (
  <a href="/settings">
    <button>Settings</button>
  </a>
);