Skip to content

Line states

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.

Readers see

loop.py
Note:import sys
Error:for name in sys.argv[1:] SyntaxError: expected ':'
print(name)
Warning:count = len(sys.argv) Includes the script name

You write

```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 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.

This site defines a todo state, as in the custom state example. Use it to show readers the parts of a file that they must complete:

handler.ts
export async function handler(request: Request) {
To do: const body = await request.json(); Check the body against a schema
return Response.json({ ok: true });
}

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

astro.config.mjs
import starlight from '@astrojs/starlight';
import codeblocks from 'starlight-codeblocks';Import the plugin
export default defineConfig({
integrations: [starlight({ plugins: [codeblocks()] })],Load the plugin
});

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:

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:

config.py
Error:retries = -1Must be 0 or more
Warning:timeout = 0.5Less than a second

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

retry.js
export async function fetchWithRetry(url) {
Warning: while (true) {
Warning: try { return await fetch(url); } catch {}
Warning: }
}
  • 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.

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

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
{}

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

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.

    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.

  • A message is one line of text. A long message makes the line wider, and the block scrolls sideways.
  • Focus: blur the other lines, so readers look at a few lines first.
  • Inline callouts: show a longer note in a bubble above a line.
  • Annotations: add a numbered marker that opens a note.