---
title: Format code
description: Fenced code blocks, the fence options Reed honours, and when to use CodeBlock and CodeGroup.
---

Reed renders fenced code blocks with Expressive Code, the engine Starlight uses. You get
syntax highlighting, a copy button, line numbers and a header label on every block with no
configuration. This page lists the fence options that work, and the ones from other
platforms that are silently ignored.

## Inline code

Wrap a word or phrase in single backticks:

```mdx
Set `REED_TOKEN` before you run `reed build`.
```

If the code itself contains a backtick, wrap it in double backticks with a space inside
each end: ``` `` `code` `` ```.

Use inline code for anything a reader might type, paste or search for: commands, file
names, environment variables, option names and literal values.

## Fenced code blocks

Wrap the block in triple backticks and put the language right after the opening fence:

````mdx
```js
const client = new Client({ apiKey: process.env.API_KEY });
```
````

**Always tag the language.** The tag drives syntax highlighting, and Reed shows it as the
block's header label when the block has no title. An untagged fence renders as plain text
with no highlighting and an empty header. For output, logs or anything that is not code,
tag it `text`.

Every block gets:

- **A copy button.** It copies the code without line numbers. In terminal blocks, comment
  lines are left out of the copied text.
- **Line numbers.** They are on by default.
- **A header.** It shows the `title` if you set one, and the language otherwise.

Blocks tagged with a shell language, such as `sh`, `bash` or `console`, render in a
terminal-style frame automatically. For commands a reader will paste, use `sh` or `bash`
and leave out the `$` prompt: the copy button copies a `console` block's `$` along with
the command.

## Fence options

Options go on the opening fence, after the language, separated by spaces. Several can be
combined:

````mdx
```js title="server.js" {3} showLineNumbers=false
import express from 'express';
const app = express();
app.listen(3000);
```
````

Each option below was checked against a real Reed build.

| Option | Example | Effect |
|---|---|---|
| Title | `title="server.js"` | Shows the value in the header, replacing the language label |
| Line highlight | `{1,3-4}` | Highlights lines 1, 3 and 4 |
| Inserted lines | `ins={2}` | Marks line 2 as added |
| Deleted lines | `del={3}` | Marks line 3 as removed |
| Text highlight | `"apiKey"` | Highlights every occurrence of the quoted text |
| Hide line numbers | `showLineNumbers=false` | Removes the line-number gutter |
| Start line | `startLineNumber=10` | Numbers the first line 10 |
| Wrap | `wrap` | Wraps long lines instead of scrolling sideways |
| Terminal frame | `frame="terminal"` | Forces the terminal-style frame |
| No frame | `frame="none"` | Removes the header, including the language label |

### File-name comments

A comment on the first line that looks like a file path becomes the block's title, and the
comment is removed from the code:

````mdx
```js
// src/server.js
const app = express();
```
````

This renders with `src/server.js` in the header and one line of code. Use `title` when
you want the label without relying on the comment's shape.

### Line highlighting

Put line numbers and ranges in braces:

````mdx
```js {2-3}
const app = express();
app.use(auth());
app.use(rateLimit());
app.listen(3000);
```
````

Here is the same block, rendered:

```js {2-3}
const app = express();
app.use(auth());
app.use(rateLimit());
app.listen(3000);
```

### Show a change

For a before-and-after on specific lines, mark them with `ins` and `del`. Both take the
same range syntax as highlighting:

````mdx
```js ins={2} del={3}
const app = express();
app.use(auth({ strict: true }));
app.use(auth());
```
````

To show a whole patch, use the `diff` language. Lines starting with `+` and `-` are
marked. Add `lang` to keep syntax highlighting for the underlying language:

````mdx
```diff lang="js"
- const timeout = 5000;
+ const timeout = 30000;
```
````

### Options that do not work

These are accepted without an error and have no effect. They come from other platforms,
mostly Mintlify, and Reed does not implement them:

| Option | Where it comes from |
|---|---|
| `highlight={1}` | Mintlify. Use `{1}` instead. |
| `focus={2}` | Mintlify. There is no fence equivalent. `CodeBlock` has a `focus` prop. |
| `lines` | Mintlify. Line numbers are already on. |
| `icon="..."` | Mintlify |
| `expandable` | Mintlify |
| `collapse={1-3}` | An Expressive Code plugin Reed does not load |
| `// [!code highlight]`, `// [!code ++]` | Shiki's comment notation. The comment is rendered as written. |

`showLineNumbers` on its own is harmless but does nothing, because line numbers are
already on.

## Fences inside components

MDX parses a fenced block inside a JSX element differently from one at the top level, and
it is a common source of confusing build failures. Keep fences at the top level of the
page when you can. Put the prose in the component and the code directly after it. When a
fence does belong inside a component, give the tags and the fence their own lines. The
exact forms that fail, and the fix for each, are in
[Fix "Expected a closing tag" for a code block inside a component](/docs/troubleshooting/fenced-code-in-component).
See also [MDX pitfalls](/docs/authoring/mdx-pitfalls).

Fences inside list items are fine. Indent the fence to line up with the item's text.

## Several languages

When you show the same operation in several languages, use `CodeGroup`. Each fenced child
becomes one tab:

````mdx
<CodeGroup>

```js title="JavaScript"
await client.projects.list();
```

```python title="Python"
client.projects.list()
```

</CodeGroup>
````

Give the tags and each fence their own lines, with blank lines between them.

Each tab is labelled from its own child: the fence's `title` first, then its language tag
exactly as written, then `Tab 1`, `Tab 2` and so on. Title each fence when the language tag
is not the name you want readers to see, such as `js` for JavaScript. A group can mix
fences and `CodeBlock` elements. The full rules are in
[Steps, tabs and code](/docs/reference/components/procedural).

Two fences in a row in different languages, with no `CodeGroup`, raise `DS-D03`, a warning
suggesting `CodeGroup`. It does not fail a build unless you pass `--strict-components`.

If you want the blocks stacked rather than tabbed, give each a `title` naming its language
and put a short sentence between them, such as "In Python:". The sentence ends the run of
adjacent fences, so `DS-D03` does not fire.

## The `CodeBlock` component

`CodeBlock` is the JSX form of a single block. It renders through Expressive Code like a
fence, with the same highlighting, header and copy button, and shows the code exactly as
written. Its props, including `highlight`, `focus`, `maxLines` and `wordWrap`, are listed
in [Steps, tabs and code](/docs/reference/components/procedural).

It takes the code in one of three forms, in this order:

- **The `code` prop**, as a string or template literal. Use it for code with quotes or
  braces:

  ```mdx
  <CodeBlock language="ts" focus="1" code={`const x = { mode: "strict" };`} />
  ```

- **A single fenced child**, with the tags and the fence on their own lines. The fence's
  language tag is used when there is no `language` prop.
- **Loose text** between the tags, such as `<CodeBlock language="sh">echo "hi"</CodeBlock>`.
  Straight quotes stay straight and an entity such as `&amp;` becomes `&`, but a bare `{`
  still opens a JavaScript expression and fails the page with `unparseable-mdx`. Use the `code` prop or a fence for
  code with braces.

A plain fence is still the simplest choice. Fences cover title, line highlighting, line
numbers, start line and wrapping. Reach for `CodeBlock` when you need `focus` or
`maxLines`.

## Related

- [Format text](/docs/authoring/formatting/text)
- [Lists and tables](/docs/authoring/formatting/lists-and-tables)
- [Steps, tabs and code](/docs/reference/components/procedural)
- [Diagnostics](/docs/reference/diagnostics)
