mermaid-js/mermaid · error · Error
Error: State name must be a single word. Found: "${yytext.tr
Error message
Error: State name must be a single word. Found: "${yytext.trim()}" What it means
Thrown at stateDiagram.jison:133 by the lexer rule <STATE>\w+\s+\w+.*?\{ while the lexer is in the STATE state (pushed by the 'state' keyword rule at stateDiagram.jison:114). The rule matches a word, whitespace, another word, then anything up to an opening brace and rejects it, because a composite state (one with a {...} body) must use either a single-word identifier or a quoted label. A bare multi-word name before the brace is ambiguous with the ID/DESCR transitions and is therefore disallowed. The thrown value is a plain Error whose message echoes the offending yytext.
Source
Thrown at packages/mermaid/src/diagrams/state/parser/stateDiagram.jison:133
<STATE>.*"<<fork>>" {this.popState();yytext=yytext.slice(0,-8).trim(); /*console.warn('Fork Fork: ',yytext);*/return 'FORK';}
<STATE>.*"<<join>>" {this.popState();yytext=yytext.slice(0,-8).trim();/*console.warn('Fork Join: ',yytext);*/return 'JOIN';}
<STATE>.*"<<choice>>" {this.popState();yytext=yytext.slice(0,-10).trim();/*console.warn('Fork Join: ',yytext);*/return 'CHOICE';}
<STATE>.*"[[fork]]" {this.popState();yytext=yytext.slice(0,-8).trim();/*console.warn('Fork Fork: ',yytext);*/return 'FORK';}
<STATE>.*"[[join]]" {this.popState();yytext=yytext.slice(0,-8).trim();/*console.warn('Fork Join: ',yytext);*/return 'JOIN';}
<STATE>.*"[[choice]]" {this.popState();yytext=yytext.slice(0,-10).trim();/*console.warn('Fork Join: ',yytext);*/return 'CHOICE';}
<struct>.*direction\s+TB[^\n]* { return 'direction_tb';}
<struct>.*direction\s+BT[^\n]* { return 'direction_bt';}
<struct>.*direction\s+RL[^\n]* { return 'direction_rl';}
<struct>.*direction\s+LR[^\n]* { return 'direction_lr';}
<STATE>["] { /* console.log('Starting STATE_STRING'); */ this.pushState("STATE_STRING"); }
<STATE>\s*"as"\s+ { this.pushState('STATE_ID'); /* console.log('pushState(STATE_ID)'); */ return "AS"; }
<STATE_ID>[^\n\{]* { if (!processId()) return; this.popState(); /* console.log('STATE_ID', yytext); */ return "ID"; }
<STATE_STRING>["] { this.popState(); }
<STATE_STRING>[^"]* { /* console.log('Long description:', yytext); */ return "STATE_DESCR"; }
<STATE>\w+\s+\w+.*?\{ { throw new Error('Error: State name must be a single word. Found: "' + yytext.trim() + '"'); }
<STATE>[^\n\s\{]+ { /* console.log('COMPOSIT_STATE', yytext); */ return 'COMPOSIT_STATE'; }
<STATE>\n { this.popState(); }
<INITIAL,STATE>\{ { this.popState(); this.pushState('struct'); /* console.log('begin struct', yytext); */ return 'STRUCT_START'; }
<struct>\} { /*console.log('Ending struct');*/ this.popState(); return 'STRUCT_STOP';} }
<struct>[\n] /* nothing */
<INITIAL,struct>"note"\s+ { this.begin('NOTE'); return 'note'; }
<NOTE>"left of" { this.popState(); this.pushState('NOTE_ID'); return 'left_of'; }
<NOTE>"right of" { this.popState(); this.pushState('NOTE_ID'); return 'right_of'; }
<NOTE>\" { this.popState(); this.pushState('FLOATING_NOTE'); }
<FLOATING_NOTE>\s*"as"\s* { this.popState(); this.pushState('FLOATING_NOTE_ID'); return "AS"; }
<FLOATING_NOTE>["] /**/
<FLOATING_NOTE>[^"]* { /* console.log('Floating note text: ', yytext); */ return "NOTE_TEXT"; }
<FLOATING_NOTE_ID>[^\n]* { if (!processId()) return; this.popState(); /* console.log('Floating note ID', yytext);*/ return "ID"; }
<NOTE_ID>\s*[^:\n\s\-]+ { if (!processId()) return; this.popState(); this.pushState('NOTE_TEXT'); /*console.log('Got ID for note', yytext);*/ return 'ID'; }
<NOTE_TEXT>\s*":"[^:\n;]+ { this.popState(); /* console.log('Got NOTE_TEXT for note',yytext);*/yytext = yytext.substr(2).trim(); return 'NOTE_TEXT'; }
<NOTE_TEXT>[\s\S]*?\n\s*"end note" { this.popState(); /* console.log('Got NOTE_TEXT for note',yytext);*/yytext = yytext.slice(0,-8).trim(); return 'NOTE_TEXT'; }
View on GitHub (pinned to d93e9c88c0)
Solutions
- Quote the multi-word label: state "Foo Bar" { ... }
- Use an explicit alias: state "Foo Bar" as FB { ... }
- Use a single-word identifier with no space: state FooBar { ... }
Example fix
// before
stateDiagram-v2
state Foo Bar {
[*] --> Bar
}
// after
stateDiagram-v2
state "Foo Bar" {
[*] --> Bar
} Defensive patterns
Strategy: validation
Validate before calling
// Catches the 'state <word> <word> ... {' shape before the lexer throws.
function validateCompositeStateNames(src) {
for (const line of src.split('\n')) {
if (/^\s*state\s+\w+\s+\w+.*\{/.test(line)) {
throw new Error('Composite state name must be quoted or single-word: ' + line.trim());
}
}
} Type guard
function isStateNameError(e) { return e instanceof Error && /State name must be a single word/.test(e.message); } Try / catch
try {
stateParser.parse(src);
} catch (e) {
if (e instanceof Error && /State name must be a single word/.test(e.message)) {
// prompt the user to quote multi-word composite state names
} else throw e;
} Prevention
- Always quote multi-word state labels, even for non-composite states, to stay safe across grammar revisions.
- Prefer 'state "Label" as ID' so the display name and identifier are explicit.
- Lint generated stateDiagram sources with the regex above before rendering.
- When accepting diagram text from users, show the exact offending line from the error message.
When it happens
Trigger: A stateDiagram or stateDiagram-v2 source containing a composite state whose name has a space and is neither quoted nor given an 'as' alias, e.g. 'state Foo Bar { ... }'. The lexer is in STATE mode when it matches word-space-word-...-{ and throws immediately during lexing, before the grammar reduces.
Common situations: Authoring a nested/composite state with a descriptive multi-word label and forgetting the quotes; assuming the simple-ID rule allows spaces; migrating from another tool's syntax that permitted spaces in state names; confusing the display label with the internal identifier.
Related errors
- union requires multiple identifiers
- text requires set
- Parsing failed: ${lexerErrors} ${parserErrors}
- Diagram ${type} not found.
- No diagram type detected matching given configuration for te
AI-assisted analysis of mermaid-js/mermaid@d93e9c88c0 (2026-08-12).
Data as JSON: /api/errors/1a9181c46b3d673f.
Report an issue: GitHub.