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.
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.
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.
// 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>
);
}// 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.
// 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 singleReact.createElement(React.Fragment, ...)call, exit code 0. - TypeScript's compiler (
tsc, strict off,jsx: react-jsx): the unwrapped.tsxfile fails withTS2657: 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 buildon this version): the unwrapped page fails withFailed to compileand a genericExpected ','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:
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
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Continue Reading
Fix: error while hydrating, root switched to client render
Why this error takes down the whole page instead of one component, and the two separate causes behind it: broken DOM nesting and no Suspense boundary above the failure.
Fix localStorage is not defined after 'use client' (Next.js)
The cookie banner from the Stack Overflow question, rebuilt on Next.js 16.3.8: why 'use client' does not stop the crash, the useEffect fix, and the other errors Node 25 and 26 print.
use client vs use server in Next.js: The Real Difference
The #1 misconception in the App Router: "use server" is not the opposite of "use client". One marks a client boundary in the module graph; the other exposes callable server functions. Server Components are the default — no directive needed.


