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
Note:import sysNote
Error:for name in sys.argv[1:]Error SyntaxError: expected ':' print(name)
Warning:count = len(sys.argv)Warning Includes the script nameYou 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
Section titled “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
Section titled “Examples”A custom state
Section titled “A custom state”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:
export async function handler(request: Request) {To do: const body = await request.json();To do Check the body against a schema return Response.json({ ok: true });}```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
Section titled “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:
import starlight from '@astrojs/starlight';import codeblocks from 'starlight-codeblocks';Import the plugin
export default defineConfig({ integrations: [starlight({ plugins: [codeblocks()] })],Load the plugin});```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
Section titled “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:
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:
Error:retries = -1Must be 0 or moreWarning:timeout = 0.5Less than a second```py title="config.py" lineStates.prefix=falseretries = -1 # [!code error] Must be 0 or moretimeout = 0.5 # [!code warning] Less than a second```States without a message
Section titled “States without a message”An attribute marks lines without a message. Use it when the text around the block explains the problem:
export async function fetchWithRetry(url) {Warning: while (true) {WarningWarning: try { return await fetch(url); } catch {}Warning: }}```js title="retry.js" warning={2-4}export async function fetchWithRetry(url) { while (true) { try { return await fetch(url); } catch {} }}```Behaviour
Section titled “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, orlineStates.prefix=falseon 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. WithlineStates: false, the message stays in the code as a comment.
- 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
- 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
Section titled “Options”lineStates
Section titled “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
Section titled “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
Section titled “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
Section titled “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.
-
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' } },},},}); -
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.
Limitations
Section titled “Limitations”- A message is one line of text. A long message makes the line wider, and the block scrolls sideways.
Related
Section titled “Related”- 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.