---
title: "Line states"
description: "Tint lines as errors, warnings, notes or successes, with an optional message after the code, like the diagnostics in a code editor."
url: "https://ewels.github.io/starlight-codeblocks/features/line-states/"
markdown: "https://ewels.github.io/starlight-codeblocks/features/line-states.md"
section: "Draw attention"
site: "starlight-codeblocks documentation"
context: "This page is from the documentation of starlight-codeblocks. A Starlight plugin that adds focus, line states, annotations, API auto-linking and 22 more features to the code blocks of a site."
index: "https://ewels.github.io/starlight-codeblocks/llms.txt"
---

# Line states

> Tint lines as errors, warnings, notes or successes, with an optional message after the code, like the diagnostics in a code editor.

Expressive Code can mark a line, but a mark does not say what is wrong with the line. A line state tints the line as an error, a warning, a note or a success. It can also show a message after the code, as a code editor does.

````md
```py title="loop.py" info={1}
import sys

for name in sys.argv[1:]  # [!code error] SyntaxError: expected ':'
    print(name)

count = len(sys.argv)  # [!code warning] Includes the script name
```
````

Each state has its own colour and a bar on the left edge. The text after the directive becomes the message. The copy button copies the code without the messages.

## Syntax

| Syntax | Where |
|---|---|
| `error={range}`, `warning={range}`, `info={range}`, `success={range}` | Code block fence line |
| `[!code error] <message>` | Comment |
| `[!code warning] <message>` | Comment |
| `[!code info] <message>` | Comment |
| `[!code success] <message>` | Comment |
| `note`, `warn` | Both |
| `[!code ++] <message>`, `[!code --] <message>`, `[!code highlight] <message>` | Comment |
| `[!code <state>:N] <message>` | Comment |
| `<state>={range}`, `[!code <state>]` | Both |
| `lineStates.prefix=false`, `lineStates.prefix=true` | Code block fence line |

`note` is another name for `info`, and `warn` for `warning`.

The message is optional. An attribute tints the lines and shows no message. `[!code <state>:N]` tints its own line and the next N-1 lines, and shows the message on the first line.

Inline code, links and bold in a message render as HTML. Other Markdown stays as text.

## Examples

### A custom state

This site defines a `todo` state, as in the [custom state example](#add-a-custom-state). Use it to show readers the parts of a file that they must complete:

````md
```ts title="handler.ts"
export async function handler(request: Request) {
  const body = await request.json(); // [!code todo] Check the body against a schema
  return Response.json({ ok: true });
}
```
````

### A message on an inserted line

A message after `[!code ++]` tells readers why a line is new, for example in a step of a tutorial:

````md
```js title="astro.config.mjs"
import starlight from '@astrojs/starlight';
import codeblocks from 'starlight-codeblocks'; // [!code ++] Import the plugin

export default defineConfig({
  integrations: [starlight({ plugins: [codeblocks()] })], // [!code ++] Load the plugin
});
```
````

### Labels without the name of the state

With `prefix: false`, a label shows only the message, and a line without a message shows only the tint. Screen readers still hear the name of the state:

```js title="astro.config.mjs"
codeblocks({
  lineStates: { prefix: false },
});
```

To leave the names out of one block only, add `lineStates.prefix=false` to its fence line. `lineStates.prefix=true` shows them in one block when the option is off:

````md
```py title="config.py" lineStates.prefix=false
retries = -1  # [!code error] Must be 0 or more
timeout = 0.5  # [!code warning] Less than a second
```
````

### States without a message

An attribute marks lines without a message. Use it when the text around the block explains the problem:

````md
```js title="retry.js" warning={2-4}
export async function fetchWithRetry(url) {
  while (true) {
    try { return await fetch(url); } catch {}
  }
}
```
````

## Behaviour

- Tints and bars:
  - Each state has a background tint and a 3 px bar on the left edge.
  - If every line of a block has the same state, the tint covers the whole block, with one bar down its side.
  - A line can have more than one state. The line then shows the colour of the last state in the configuration.
- Labels:
  - A message shows after the code, as a small label. The label starts with the name of the state in bold: **Error**, **Warning**, **Note** or **Success**. With `prefix: false`, or `lineStates.prefix=false` on the fence line (the first line of the code block, with the language), the label shows only the message.
  - A line with no message shows a label with only the name of the state. In a group of lines with the same state, only the first line shows it. The colour then does not carry the meaning alone. With the names off, a line with no message shows only the tint.
  - A message after `[!code ++]`, `[!code --]` or `[!code highlight]` shows in the same label, in the colour of the marker. The label has no name before the message, because Expressive Code already marks the line. With `lineStates: false`, the message stays in the code as a comment.
- Screen readers:
  - Screen readers hear the name of the state before the line, for example "Error:". A screen reader reads the message after the code.
- Copy:
  - The copy button leaves out the messages. The names of the states and the messages are also not part of a manual text selection.
- Without JavaScript:
  - Line states need no JavaScript in the browser.

## Options

### `lineStates`

Tints lines as errors, warnings, notes or successes, with an optional message. Set it to `false` to turn the feature off. The directives then stay in the code as written.

- Type: `false | object`
- Default: On

### `lineStates.states`

Custom states by name, in addition to `error`, `warning`, `info` and `success`. A name uses lower-case letters, digits and hyphens.

- Type: `Record<string, { label: string; colour: { dark: string; light: string } }>`
- Default: `{}`

### `lineStates.prefix`

Show the name of the state, such as **Error**, before each message, and on the first line of a run with no message. Off, the tint alone marks the state on screen. A code block can set its own on its fence line.

- Type: `boolean`
- Default: `true`

### Add a custom state

A custom state works in the same way as a built-in state. Its name is the attribute and the directive.

1. Add the state to `lineStates.states`. Give it a label, and a colour for the dark and the light theme.

   ```js title="astro.config.mjs"
   codeblocks({
     lineStates: {
       states: {
         todo: { label: 'To do', colour: { dark: '#c792ea', light: '#7c3aed' } },
       },
     },
   });
   ```

2. Use the name as an attribute, such as `todo={9}`, or as a directive, such as `[!code todo]`.

A name uses lower-case letters, digits and hyphens. It cannot be the name of another attribute, such as `title` or `focus`. To change the label or the colour of a built-in state, add a state with its name, for example `info`.

The plugin makes the tint and the label colours from the colour that you give. Each colour must have a contrast of 3:1 or more against the background of the code, so that readers can see the bar.

- [Configuration](https://ewels.github.io/starlight-codeblocks/configuration/): Learn how to set options for the whole site, or for a single code block.

## Limitations

- A message is one line of text. A long message makes the line wider, and the block scrolls sideways.

## Related

- [Focus](https://ewels.github.io/starlight-codeblocks/features/focus/): blur the other lines, so readers look at a few lines first.
- [Inline callouts](https://ewels.github.io/starlight-codeblocks/features/inline-callouts/): show a longer note in a bubble above a line.
- [Annotations](https://ewels.github.io/starlight-codeblocks/features/annotations/): add a numbered marker that opens a note.
