Skip to content

Fix "Expected a closing tag" for a code block inside a component

A fenced code block that starts or ends on the same line as a JSX tag breaks MDX parsing. Give the fence its own lines.

You put a code block inside a Card, Step, Tab or Accordion, and reed check or reed build fails with unparseable-mdx:

txt
✗ [unparseable-mdx] content file "guides/setup.mdx" is not parseable: Expected a closing tag for `<Card>` (4:1-4:23) before the end of `paragraph`

or, without the trailing clause:

txt
✗ [unparseable-mdx] content file "guides/setup.mdx" is not parseable: Expected a closing tag for `<Card>` (4:1-4:23)

reed build prints the same line and then stops before the site build starts:

txt
build stopped before astro build due to blocking diagnostics.

The closing tag is right there in your file, which is what makes this confusing. The same code block works fine one line higher up, outside the component.

The position in parentheses counts lines from the end of the frontmatter, not from the top of the file. On a page with four lines of frontmatter, 4:1 is line 8 in your editor.

MDX decides whether a JSX tag is a block or inline element by what shares its line. A fenced code block is a block, and it cannot live inside inline content.

Three layouts trigger the failure.

The element becomes inline, part of a paragraph. The fence then ends the paragraph before the closing tag arrives — hence before the end of paragraph.

mdx
<Card title="Install">```sh
npm install
```</Card>

Prose on the opening-tag line, fence below it

Section titled “Prose on the opening-tag line, fence below it”

Same cause, less obvious:

mdx
<Step title="Install">Run this:
```sh
npm install
```
</Step>

```</Card> is not a closing fence, so the code block never ends. It swallows </Card> and everything after it, and the parser reaches the end of the file still looking for the closing tag. This is the message without before the end of paragraph.

mdx
<Card title="Install">
```sh
npm install
```</Card>

Give every tag and every fence line its own line, with blank lines around the fence:

mdx
<Card title="Install">
```sh
npm install
```
</Card>

For a step, put the lead-in sentence on its own line too:

mdx
<Steps>
<Step title="Install">
Run this:
```sh
npm install
```
</Step>
<Step title="Build">Run the build.</Step>
</Steps>

The blank lines are not strictly required — a bare newline after the opening tag is enough to parse — but they keep the layout unambiguous when someone edits the block later.

Better still, keep the fence out of the component entirely. Put a short sentence in the component and the code block directly after it at the top level of the page. That is the layout Reed’s own documentation uses, and it cannot hit this failure at all.

If a page has several of these, fix them all before re-running. unparseable-mdx stops parsing that file, so none of its other diagnostics are reported until it parses.

Run the check and confirm the parse error is gone:

sh
reed check 2>&1 | grep -c 'unparseable-mdx'

Expect 0. The page may now report ordinary DS-* diagnostics that the parse failure was hiding; fix those as usual. Then run reed build and open the page: the code block should render inside the component with its language label and copy button, not as literal backticks.