Years in the past, once I learn Sarah Drasner’s article on making a VS Code theme, I silently thought to myself, That’s loads of work… I’m by no means going to make a theme…
However lo and behold, I went forward and made one — and it took lower than six hours to get a lot of the theme working, then a day or two to shine up my last tweaks.

On this article, I wish to you stroll you thru my course of of making this theme — together with the precise steps I took to create it.
I believe speaking concerning the course of is highly effective as a result of I went from Nah, an excessive amount of work
to Oh, I can do it to It’s accomplished..?
all inside a matter of hours. (The remainder is solely time spent sprucing).
I by no means wished to make a VS Code theme…
I used to be in the midst of redesigning my web site. I’ve been rocking a brilliant duper outdated design that I’ve wished to vary for years — and I lastly began transferring.

I used Dracula Theme for code snippets in my outdated design and it labored since Dracula was the one factor that supplied a splash of colour in my in any other case stark design.
But it surely didn’t work nicely with my new website design.

All I wished to do was to enhance syntax highlighting for the code blocks so that they’re extra aligned with the remainder of the location.
That was the start of every little thing.
Shiki CSS variable theming made it easy
I exploit Astro for my web site. Shiki is a syntax highlighter that’s constructed into Astro by default.
With some fast analysis, I spotted Shiki lets you create themes with CSS variables — and there are solely a handful of colours we have to select.

That doesn’t sound too sophisticated, so I acquired AI to assist flesh out a Shiki theme based mostly on the CSS variables. Right here’s the CSS and JavaScript you want for those who’re utilizing Astro as nicely:
:root {
--shiki-foreground: #eeeeee;
--shiki-background: #333333;
--shiki-token-constant: #660000;
--shiki-token-string: #770000;
--shiki-token-comment: #880000;
--shiki-token-keyword: #990000;
--shiki-token-parameter: #aa0000;
--shiki-token-function: #bb0000;
--shiki-token-string-expression: #cc0000;
--shiki-token-punctuation: #dd0000;
--shiki-token-link: #ee0000;
}
pre.shiki,
pre.astro-code {
padding: 1rem;
border-radius: 0.5rem;
colour: var(--shiki-foreground);
background-color: var(--shiki-background);
overflow-x: auto;
}
pre.shiki code,
pre.astro-code code {
padding: 0;
font-size: inherit;
line-height: inherit;
colour: inherit;
background: none;
}
import { createCssVariablesTheme } from 'shiki/core'
const shikiVariableTheme = createCssVariablesTheme({
title: 'css-variables',
variablePrefix: '--shiki-',
fontStyle: true,
})
export default defineConfig ({
// ...
markdown: {
shikiConfig: {
theme: shikiVariableTheme
}
}
})
I did a fast experiment with the colours I had already used for my web site and in contrast it to varied in style themes, like Dracula, Sarah’s Evening Owl, and Moonlight 2.
This gave me the arrogance to push my very own theme somewhat additional — as a result of the syntax highlighting was shaping up in the correct route.
However, to push this additional, I needed to ditch CSS variable theming and dive into TextMate tokens. It was important as a result of sure code blocks appeared completely horrendous and TextMate tokens present extra granular management of how and what will get colour.
That is the place the “exhausting” half begins.
Getting AI to assist with TextMate scopes
Fortunately, AI is right here to assist. If AI wasn’t right here, I might need simply given up at this level.
Right here’s what I acquired my AI to do:
- I stated I wished to make a customized theme.
- I instructed it to create a scaffold for me.
- I requested it to search for Moonlight 2’s theme recordsdata as a reference and create the TextMate scope tokens based mostly on that.
I acquired it to consolidate the colours used into semantic key phrases like foreground, background, key phrase — just like the Shiki CSS variable theme.
And I requested it to drag all the colours right into a colour object so I can have a palette object that features solely the semantic names.
Right here’s roughly what it created:
const colours = {
purple: '...',
blue: '...',
// ...
}
const palette = {
foreground: '...',
background: '...',
// ...
}
export default {
colours: {
// Used for theming the textual content editor
},
displayName: 'Show Title of your Theme',
title: 'your-theme-name',
tokenColors: [
{
name: 'Scope name (optional)',
scope: [/*scopes used*/],
settings: {
foreground: /* change colour */,
background: /* background of the textual content */,
fontStyle: /* regular, daring or italic */,
}
}
]
}
You’ll want to present JSON for VS Code to configure issues, so I additionally acquired AI to create a construct script that converts the above format right into a .json file.
You could find the construct script and every little thing I used within the GitHub Repo.
Debugging domestically
It was unimaginable to debug syntax highlighting on my web site as a result of I needed to manually restart the server every time I modified a variable.
So, I requested AI for a suggestion.
It stated that I can use VS Code’s Extension Host for native improvement, then proceeded to created a .vscode/launch.json file with the next contents:
{
"model": "0.2.0",
"configurations": [
{
"name": "Extension",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
]
}
]
}
To run this, you need to use F5 (Home windows) or Fn + F5 (Mac) and a brand new editor window will pop up — on this new window, you may change the theme to your customized theme.
Recognizing a window that makes use of the extension host is kind of easy as a result of:
- If you happen to change your theme, that window will probably be a special theme in comparison with your different opened textual content editors.
- The Extension Host key phrase is outstanding within the title.

Now, every little thing has been a blur at this level, so I can’t keep in mind if it’s essential embrace the next into your bundle.json file for theme switching to work within the extension host. In that case, embrace it:
{
"contributes": {
"themes": [
{
"label": "Your Theme Name",
"uiTheme": "vs-dark",
"path": "<path-to-your-theme>.json"
}
]
}
}
Understanding TextMate scopes
At first, I copy-pasted photographs and tried to get AI to regulate varied tokens to the colours I selected. But it surely acquired irritating fairly shortly.
Both:
- the AI acquired the textmate scope incorrect, or
- it was overwritten by one thing else.
I couldn’t inform. However fortunately you may debug the TextMate scopes simply with a “Developer: Inspector Editor Tokens and Scopes” command.

While you’re on this mode, you may click on on any textual content and a window will pop up. This incorporates all the data it’s essential alter TextMate scopes.

Right here’s easy methods to learn what’s happening:
- Foreground: Tells you the present lively scope. On this case, the lively scope is
variable. - TextMate scopes: Tells you what are the obtainable TextMate scopes you need to use for this particular token.
TextMate scopes work in an fascinating manner. I found out the next by experimenting, so it won’t be 100% correct:
- You should use any a part of the obtainable scopes.
variable,variable.prop, andvariable.prop.cssall work. - You’ll be able to improve specificity by stating extra properties.
variable.prop.css>variable.prop>variableby way of specificity. - The upper scope is extra particular than the decrease one.
variable>meta.operate.misc.css. - You’ll be able to different scopes with them like CSS selectors if it’s essential overwrite the next scope.
meta.operate variable>variable
How I selected colours for the theme
That is an important matter when making a theme. There’s no level having the theme if syntax highlighting doesn’t assist the developer in studying code.
Two articles come into my thoughts right here:
Primarily, the ideas that I took away from each articles are:
- We would like highlights to face out.
- Colours will look similar to one another for those who make use the identical lightness and chroma, and it’ll be exhausting to inform them aside.
- If every little thing is highlighted, nothing is highlighted.
- If every little thing is essential, nothing is.
Principally, we’re speaking about the precept of distinction when designing. Since I’m already designing for somebody to learn, the very subsequent ideas that got here have been:
- How do I information my eyes?
- What are essential parts that I’ve to see/know?
- What parts are much less essential?
With that, I started working:
Featuresandstrategieshave been essential so that they needed to be robust, so I usedcyanwhich is the strongest colour in my palette.- The
exportkey phrase can also be essential because it signifies an export! Key phraseslikeimportandoperatecould be reasonably muted, sopurpleit’s.Stringscould beinexperienced— cos they appear reasonably pleasing in an inventory of textual content inside a JSON file.

I performed round with the remainder of the colours somewhat, however I finally settled with the next:
Constantsareorangeas a result of it’s kinda simple to identify themVariablesarewhite-ish as a result of that’s the majority of the textual content — including colours to them creates the “Christmas Lights Diarrhea” impact Tonsky talked about.Propertiesareblueas a result of they’re like workhorses that wants colour differentiation, however not sufficient to attract an excessive amount of consideration.

Then I moved onto HTML/Astro/Svelte:
Tagsare crimson as a result of they’re kinda essential — and crimson is less complicated to learn that cyan.Attributesarepurplefor a similar cause askey phrases.Partsareorangeas a result of they have to be totally different fromTags.- Bonus factors:
TagsandPartsare associated — socrimsonandorangefeels good right here.

And, lastly, CSS syntax highlighting. Virtually every little thing appeared proper at this level, besides that:
CSS Featuresneeds to becyanlike that in JS.Punctuationneeds to be muted so we are able to simply differentiate the--from the remainder of the textual content.Propertycould beinexperiencedas a result of blue is simply too boring on this context — andinexperiencedis good on the eyes when contrasted with different highly effective colours.

It’s a pity that syntax highlighting for nested courses goes somewhat bit haywire (they’re inexperienced, however they need to be orange), however there’s nothing a lot I can do about it.

Debugging colours
VS Code is constructed on Electron, so it’s simple to debug and check colours. What I needed to do was hearth up devtools, examine the colour I wished to vary, and alter them on to get a dwell replace!
Wrapping up
An important I factor I discovered throughout this course of is to float. One opening can result in one other, then one other, and one thing what appears “unimaginable” can develop into “Oh, it’s accomplished?” in a matter of hours.
I name my theme Twilight Cosmos (AI helped with the naming). You could find it on:
How did I publish my extension? That’s the topic of a quick follow-up article that I’m engaged on.
Within the meantime, right here’s the GitHub repo if you wish to construct upon no matter I’ve accomplished. Be happy to counsel edits to enhance this theme too!
Lastly, join my e mail e-newsletter for those who’re occupied with listening to my creation adventures. 🙂
That’s it. Thanks for studying and I hope you had a blast!

