Designers don't eyeball a type system. You pick a ratio, you pick a base, you assign each size a role, and the scale falls out. Editor themes don't work that way. Most of them are a mood board — this color feels like a keyword— and hoped to hold together at 2am on a 13" laptop.
// parse and validate a semver string export function parseVersion(input: string) { const match = input.match(/^(\d+)\.(\d+)\.(\d+)$/) if (!match) throw new Error('invalid semver') return { major: +match[1] } }
- CriticalErrors, primary labelstarget 7.0 : 16.82 : 1
- StructuralKeywords, operatorstarget 5.5 : 17.61 : 1
- SemanticTypes, strings, numberstarget 4.5 : 113.12 : 1
- ContextualParameters, propertiestarget 3.5 : 18.27 : 1
- AmbientComments, indent guidestarget 2.5 : 13.90 : 1
Here's the frame I wish someone had given me years ago: an editor is a typographic document with five tiers of information, and each tier has a contrast target.
The five tiers
| Tier | Target | Examples |
|---|---|---|
| Critical | 7.0 : 1 | Errors, primary labels |
| Structural | 5.5 : 1 | Keywords, operators |
| Semantic | 4.5 : 1 | Types, strings, numbers |
| Contextual | 3.5 : 1 | Parameters, properties |
| Ambient | 2.5 : 1 | Comments, indent guides |
When you design a theme this way, you stop picking “a color for comments.” You pick a luminance for comments that hits 2.5:1 against your background, and then you push hue until it feels right. The palette serves the hierarchy, not the other way around.
- CriticalErrors · primary
- StructuralKeywords · operators
- SemanticTypes · strings
- ContextualParams · properties
- AmbientComments · guides
Luminance locked to tier.Hue & chroma stay free.
Tune in Themery — pull luminance to the tier, leave hue and chroma free.
Why this matters
Typography has been a solved design problem for a long time. We pick Geist, or Inter, or Mono; we pick 16px body and a 1.25 modular scale; we know the H1 is going to read. Code has all the same needs — hierarchy, rhythm, breathing room — and almost none of the same discipline.
The biggest practical consequence is that every design decision collapses into one number. Instead of “does this blue feel right for keywords,” you ask: does the luminance of this keyword color hit the structural tier? If yes, the hue is now free for mood. If no, move the luminance until it does, then choose the hue.
This is the same move type designers make when they pick a scale before a face. You decouple the structural decision from the aesthetic one, so each can be made on its own terms.
Apply it to an existing theme
- Grab the theme's hex values out of its repo.
- Measure contrast for each token against the background. A free tool like WebAIM works, or use the
apca-w3npm package. - Map each token to one of the five tiers above.
- For any token that misses its target, lift lightness by ~10–15%. Keep hue and chroma alone. Re-measure.
In my experience, most themes become WCAG AA-compliant with a single pass over comments and punctuation. You don't lose the mood. You gain readable code.
The tool
I built Themeryaround this idea. You give it a palette. It enforces the five tiers as a constraint at compile time — so the theme you ship is provably readable before it ever lands in someone else's editor. Same theme, every major IDE, from one source.
palette.ts// resolve export function resolve(url: string) { return `/v1/${url}` }
// resolve export function resolve(url: string) { return `/v1/${url}` }
// resolve export function resolve(url: string) { return `/v1/${url}` }
// resolve export function resolve(url: string) { return `/v1/${url}` }
One palette, compiled to every major IDE. The tier constraint runs at build time.
You don't need my tool to do this, though. You need the ruler.