Skip to content

Squiggles

A code editor draws a wavy line under code that has a problem, and shows the message when you hover over it. A code block in a page shows the code, but not the problem. A squiggle draws the same wavy line under the text, in red for an error, yellow for a warning or blue for info.

Readers see

load.ts
import { readFile } from 'node:fs/promises';
import { join (Warning: join is declared but its value is never read.)Warningjoin is declared but its value is never read. } from 'node:path';
const config = JSON.parse(await readFle (Error: Cannot find name readFle. Did you mean readFile?)ErrorCannot find name readFle. Did you mean readFile?('config.json', 'utf8'));
  • Line 2, warning: join is declared but its value is never read.
  • Line 4, error: Cannot find name readFle. Did you mean readFile?

You write

```ts title="load.ts"
import { readFile } from 'node:fs/promises';
import { join } from 'node:path'; // [!warning /join/] `join` is declared but its value is never read.
// [!error /readFle/] Cannot find name `readFle`. Did you mean `readFile`?
const config = JSON.parse(await readFle('config.json', 'utf8'));
```

Each directive is gone from the output. A wavy line is under the text between the slashes. Hover over the line to see the message, or press Tab to move to it.

Syntax Where
[!error /text/] message Comment on or above the line
[!warning /text/] message Comment on or above the line
[!info /text/] message Comment on or above the line
[!error] message Comment on or above the line
warn, note Comment on or above the line

warn is another name for warning, and note for info. The directive goes at the end of the line, or on its own line directly above it. /text/ is literal text, not a regular expression. The wavy line goes under the first match on the line that is a whole word, else under the first match. So /x/ finds x, not the x in max. Without /text/, it goes under the whole line, from the first character that is not a space.

The message is optional. It can hold inline code, links and bold text.

A squiggle without /text/ is under all of the code on its line:

import sqlite3
db = sqlite3.connect("notes.db")
db.execute("SELECT body FROM notes") (Info: Returns nothing until you call fetchall().)InfoReturns nothing until you call fetchall().
  • Line 4, info: Returns nothing until you call fetchall().

Two directives on one line draw two wavy lines, each with its own message:

for (var (Warning: Use let or const instead.)WarningUse let or const instead. i = 0; i < items.lenght (Error: Property lenght does not exist. Did you mean length?)ErrorProperty lenght does not exist. Did you mean length?; i++) render(items[i]);
  • Line 1, error: Property lenght does not exist. Did you mean length?
  • Line 1, warning: Use let or const instead.

Without a message, the wavy line is enough. A reader who hovers over it sees only the severity:

fn main() {
let unused (Warning)Warning = 42;
println!("Hello, world!");
}
  • Line 2, warning
  • How it looks:
    • The wavy line is VS Code’s own shape, under the text. Its colour comes from the theme’s error, warning or info colour.
    • The line is continuous under text in several colours, such as a function call with its arguments.
  • Opening the message:
    • Hover over the text for 300 ms to show the message. The mouse cursor can move into the message, for example to click a link.
    • Each squiggle is in the tab order. Keyboard focus shows the message at once.
    • On a touch screen, tap the text to show the message. Tap somewhere else to close it.
    • Escape closes the message. The message closes when the mouse cursor or the focus leaves.
  • Where the message opens:
    • Above the text, from its first character, as in VS Code. If there is no room above, it opens below.
    • The message starts with an icon and the name of the severity, Error, Warning or Info. The colour is not the only sign of the severity.
    • A long message wraps at the popover width, 340 px by default.
  • Screen readers:
    • Screen readers read the severity and the message after the text, such as “(Error: Cannot find name)”.
  • Copy and print:
    • The copy button copies the code without the messages. A manual selection leaves them out too.
    • In print, the wavy lines stay, and the messages are a list under the block, with their line numbers.
  • Without JavaScript:
    • The wavy lines show, but the messages do not open. Screen readers still read them.
  • Motion:
    • The message fades in and out in 120 ms. With reduced motion, it shows and hides at once.

With squiggles: false, the plugin does not know the [!error], [!warning] and [!info] directives. It leaves each comment in the code as written, and logs a build warning.

  • A squiggle is on one line. For a problem that spans lines, put one squiggle on each line.
  • /text/ finds the first match on the line. To mark a later match, make /text/ longer, so that it is unique on the line.
  • Readers see a message only when they hover over the text or focus it. For a message that is always visible, use line states.
  • Line states: tint a whole line as an error, a warning or a note, with the message always visible after the code.
  • Inline callouts: a short, always visible note above the line, with an arrow at one name.
  • Annotations: a numbered marker that opens a note in a popover.