Next.js

Adjacent JSX Elements Must Be Wrapped in an Enclosing Tag

The 2015 wording still fires in Babel today, but Next.js shows a different message for the same mistake, and wrapping the tags is not always the whole fix.

Mahdi Ben RhoumaMahdi Ben Rhouma
9 min read

TL;DR#

A function component — or any { ... } JSX expression — can only return one value. Writing two sibling tags next to each other with no shared parent, like an <input /> immediately followed by a <p>...</p>, throws a parse error before your code ever runs. Babel, TypeScript's compiler and Next.js's own compiler all reject it; each reports a different message for the identical mistake. The fix is to wrap the siblings in one parent element, or in a Fragment (<>...</>) that adds nothing to the rendered HTML.

text
SyntaxError: /work/bad.jsx: Adjacent JSX elements must be wrapped in an enclosing tag. Did you want a JSX fragment <>...</>? (6:4)

This is the exact sentence from a 2015 Stack Overflow question with 598 votes and over 617,000 views, and it is still the sentence current Babel throws for the same mistake, confirmed below.

Why it happens#

JSX is not markup; it compiles down to ordinary JavaScript function calls. React's own documentation states the reason plainly: "JSX looks like HTML, but under the hood it is transformed into plain JavaScript objects. You can't return two objects from a function without wrapping them into an array. This explains why you also can't return two JSX tags without wrapping them into another tag or a Fragment." A return statement, and a { ... } JSX expression container, can only hand back one value — so two tags side by side, with nothing joining them, are not one value.

In current Babel, the thrown error is a SyntaxError that also carries its own machine-readable name alongside the message: reasonCode: 'UnwrappedAdjacentJSXElements', confirmed in the reproduction below.

Fix#

1. The fastest fix: wrap both tags in one parent element.

text
// Before — this is not valid JSX, which is exactly the point:
function Signup() {
  return (
    <div className="form">
      <input type="email" />
    </div>
    <p className="footer">Footer text</p>
  );
}
jsx
// After — one parent element
function Signup() {
  return (
    <div>
      <div className="form">
        <input type="email" />
      </div>
      <p className="footer">Footer text</p>
    </div>
  );
}

This compiles, but it also adds a DOM node (an extra <div>) that was not there before, which can break CSS that relies on direct-child selectors or on the component sitting directly inside a <table> or a flex container.

2. The usual fix: a Fragment instead of an extra element.

jsx
// After — no extra DOM node
function Signup() {
  return (
    <>
      <div className="form">
        <input type="email" />
      </div>
      <p className="footer">Footer text</p>
    </>
  );
}

React's reference docs are explicit that <Fragment>, and its shorthand <>...</>, group elements "without a wrapper node" and that "grouping elements in Fragment has no effect on the resulting DOM; it is the same as if the elements were not grouped." The named Fragment export itself was added to React in version 16.2.0 (28 November 2017), per React's own changelog and the pull request that shipped it.

3. Rendering a list of these groups: you cannot use the shorthand. If you need a key — because you are mapping over data and each group is one item in a list — the shorthand <>...</> cannot take one. The same docs give the reason: "If you want to pass key to a Fragment, you can't use the <>...</> syntax. You have to explicitly import Fragment from 'react' and render <Fragment key={yourKey}>...</Fragment>." An array of elements also works — React's changelog records that components could return arrays and strings directly from render starting in React 16.0.0, 26 September 2017 — but every item needs a real, stable key, not the array index. If your linter flags key={index} once you switch to that form, that is a different, well-documented mistake: continue with Fix: Do Not Use Array Index in Keys.

Verify the fix#

The one-line change — adding <> and </> — was reproduced on 25 October 2026, in a node:22-alpine container, against three separate tools, each on the same markup, changing nothing else: Node v22.23.3, Babel 7.29.7 with @babel/preset-react, TypeScript 5.9.3, and Next.js 15.5.23.

  • Babel: the unwrapped file throws SyntaxError: ... Adjacent JSX elements must be wrapped in an enclosing tag, exit code 1; the wrapped file compiles to a single React.createElement(React.Fragment, ...) call, exit code 0.
  • TypeScript's compiler (tsc, strict off, jsx: react-jsx): the unwrapped .tsx file fails with TS2657: JSX expressions must have one parent element.; the wrapped file produces no diagnostics.
  • A real Next.js build, pages router, webpack (the default for next build on this version): the unwrapped page fails with Failed to compile and a generic Expected ',' token error from its compiler, SWC; after wrapping, the same build prints "Compiled successfully" and generates the static page.

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.

Wrapping it is not always the whole fix#

The original question's render body is not simply two tags side by side. It is a text line, if(this.state.submitted==false), followed by a { ... } block that holds the two elements the error complains about. That shape is worth reproducing on its own, because the top-voted answer ("put your component between an enclosing tag") only addresses half of it. The question's third answer does name the other half: "you can't use if statements inside of a returned element" — and its own fix moves the if out of the JSX entirely, into a normal if/else that returns a different tag from each branch, rather than leaving the condition as text inside the markup.

Wrapping just the two elements inside the block, and leaving the if (...) line as plain text, does clear the parse error — but rendering the result with renderToStaticMarkup (React 19, react-dom/server) shows why that is not enough: the output is identical whether the submitted value is true or false. The form renders unconditionally either way, and the literal text if(submitted==false) is printed into the page, because a bare line of text followed by an unrelated { ... } expression is two separate, unconnected JSX children — the word "if" there is not a JavaScript if statement at all, just a string.

The fix that actually makes the rendering conditional replaces the whole text-plus-brace pattern with one JSX expression:

jsx
function Signup({ submitted }) {
  return (
    <div>
      {!submitted && (
        <>
          <input type="email" className="input_field" />
          <div className="button-row">
            <a href="#" className="button">Request Invite</a>
          </div>
        </>
      )}
    </div>
  );
}

Rendered with submitted set to true, this version produces an empty <div></div>; set to false, it renders the form and nothing else — no stray text, no unconditional rendering. If your own "adjacent JSX" error sits next to an if statement written as a text line rather than inside a { ... } condition, wrapping the tags will only remove the red error; it will not make the section conditional.

A different tool, a different message#

Next.js does not compile JSX with Babel by default. Next.js's own documentation says its compiler, written in Rust using SWC, "replaces Babel for individual files" and has been "enabled by default since Next.js version 12" — Babel only comes back if the project has its own Babel config or uses a feature SWC does not support, per the same page. On Next.js 15.5.23 with no Babel config, next build on the unwrapped file above fails with Expected ',', got 'className'; running the same unwrapped file through the standalone @swc/core package (version 1.16.20) directly, outside Next.js, fails with Expected ',', got 'ident' instead. Two separate invocations, next build's bundled compiler and the standalone @swc/core package — neither names "adjacent JSX elements" the way Babel does. TypeScript's own compiler takes a third path, with its own code, TS2657, and its own sentence, JSX expressions must have one parent element. The cause and the fix are identical in all three; only the paragraph your terminal prints is different.

A signup form that tracks a submitted boolean and conditionally shows its fields, the same shape as the original question, is also exactly the kind of component that tends to pick up two other classic React mistakes once it starts doing real work: effects that misbehave when Strict Mode runs them twice in development (see Fix useEffect Running Twice in React 18), and state updates from a fetch or timer that is still pending after the component has unmounted (see Fix: Can't Perform a State Update on an Unmounted Component). If the same component also reads a stored submitted flag from localStorage in its body rather than inside an effect, wrapping the JSX correctly will not save it on Next.js, which renders Client Components on the server too before any browser code runs — that failure mode, and its fix, are covered in Fix localStorage is not defined after 'use client'.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote