Code
Code blocks are always dark
Code blocks stay dark regardless of colour mode. A code surface that flips
with the theme forces the syntax palette to be designed twice and makes
screenshots inconsistent between users. The surface is midnight-2 from the
dark scale (#131e2b) in both light and dark mode.
The block you are reading is rendered by that rule:
// Agent pipeline definition
const pipeline = createPipeline({
name: "validate-deploy",
policy: "strict",
agents: ["auditor", "deployer"],
});
Syntax theme
Because the surface is always dark, every syntax value is read from the dark
scale. Contrast is measured against #131e2b.
Ten roles, not twenty-two tokens
Editors do not describe code with six token types. The Language Server Protocol defines 22 semantic token types and 10 modifiers, and TextMate grammars — the model behind VS Code, Sublime Text, and most highlighters — define 11 root scopes with a deep sub-scope tree under each.
A theme should not answer that with twenty-two colours. Past roughly nine, hue stops being a signal: everything is coloured, so nothing is marked. The two scope vocabularies are therefore grouped into ten roles, and the modifiers are carried by weight and slant rather than by more hue.
Every role is an existing brand or terminal value. The expansion introduced no new colour — the terminal palette had already added the two hues, cyan and magenta, that a syntax theme needs and the three interface scales do not have.
| Role | On surface | LSP semantic token | TextMate scope |
|---|---|---|---|
Plain and variables#c5daf0 midnight-dark-12The default. Anything the reader does not need to pick out. | 11.74:1 | variable · parameter · property · enumMember | variable · variable.parameter · variable.other.n .nv .nx .py .vc .vg .vi |
Keywords and modifiers#8aacc8 midnight-dark-11Also structural keys — a YAML key is the keyword of its line. | 7.06:1 | keyword · modifier | keyword.control · storage.modifier · storage.type.k .kc .kd .kn .kp .kr .nt .na |
Types and classes#74c0c9 terminal cyan (bright)The brand has no cyan; the terminal palette does, and this is the second place the system needed one. | 8.11:1 | type · class · struct · interface · enum · typeParameter · namespace | entity.name.type · entity.name.class · support.class.kt .nc .nn .ne .bp |
Functions and methods#e0a92a terminal yellow (bright)Callables read as callable at a glance — the single most useful distinction beyond the original six. | 7.91:1 | function · method | entity.name.function · support.function.nf .fm |
Decorators and macros#d491b4 terminal magenta (bright)Code that runs at a different time from the code around it. | 6.82:1 | macro · decorator · event | entity.name.tag · meta.decorator · support.macro.nd .ni .nl |
Strings#ea7558 orange-dark-10Interpolation delimiters take the operator colour, so the expression inside stays readable as code. | 5.76:1 | string | string.quoted · string.interpolated · string.regexp.s .s1 .s2 .sa .sb .sc .sd .se .sh .si .sr .ss .sx .dl |
Numbers and constants#f09878 orange-dark-11Literal values, including true/false/nil. | 7.58:1 | number · regexp | constant.numeric · constant.language · constant.character.m .mb .mf .mh .mi .mo .il .no |
Operators and punctuation#97a8b8 slate-dark-11Present but recessive — structure you read past, not at. | 6.90:1 | operator | keyword.operator · punctuation.o .ow .p |
Comments#72889d code-commentItalic. The only role with no scale step of its own. | 4.59:1 | comment | comment.line · comment.block · comment.block.documentation.c .ch .cm .cp .c1 .cs |
Invalid and deprecated#f08b80 danger-darkDeprecated is struck through as well as coloured — the state does not depend on hue. | 6.98:1 | (modifier) deprecated | invalid.illegal · invalid.deprecated.err |
Modifiers are not colours
LSP modifiers combine with any token type: ten modifiers against ten roles is a hundred states. Hue cannot carry that, so it does not try.
| LSP modifier | Treatment | Why |
|---|---|---|
| declaration · definition | Weight 500 | Where a name is introduced, distinguished from where it is used. |
| deprecated | Line-through | A state, not a category — it must survive greyscale. |
| documentation | Italic, comment colour | Doc comments are comments; they are not a separate hue. |
| readonly · static · abstract · async · defaultLibrary | No distinct colour | Ten modifiers times ten roles is a hundred combinations. Colour cannot carry that, and a theme that tries becomes unreadable. |
deprecated is a state, not a category. It is struck through as well as
recoloured, so a reader who cannot separate the red from the plain text still
sees that the symbol should not be used.
Why comments have a dedicated token
Comments are the one syntax role with no scale step available to it. Steps 8–10 are border and solid-surface roles and are not held to text thresholds; step 11 is already spoken for by operators.
So code-comment (#72889d, 4.59:1) exists as a dedicated syntax token —
the dimmest value that clears AA while staying visibly below operators. It is
not a scale step and should not be treated as one.
Group scopes into roles, and let a language’s grammar map onto them. Keep the role count under ten, and check every value against the code surface.
Give each LSP token type its own hue, or use the accent as a syntax colour — it marks the primary action, and a code block is not one.
Inline code
Inline code does not take the dark block treatment — it follows the
surrounding surface. On light surfaces it sits on midnight-2 (light scale)
with orange-11 text; in dark mode both values shift to their dark-scale
counterparts. It uses IBM Plex Mono at 13px with a 3px radius.
Terminal output
Terminal blocks use the same dark surface. Prompts take the comment colour, output takes the operator colour, so a transcript stays readable without becoming a second syntax theme.
$ hugo --gc --minify
Start building sites …
Total in 842 ms