Years in the past, once I learn Sarah Drasner’s article on making a VS Code theme, I silently thought to myself, That’s lots 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 remaining 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 in regards to 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 performed..?
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 course 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 effectively with my new web site design.

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

That doesn’t sound too difficult, so I obtained 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 effectively:
: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({
identify: '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 standard themes, like Dracula, Sarah’s Evening Owl, and Moonlight 2.
This gave me the arrogance to push my very own theme a little bit additional — as a result of the syntax highlighting was shaping up in the proper path.
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 seemed completely horrendous and TextMate tokens present extra granular management of how and what will get colour.
That is the place the “onerous” 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 obtained 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 obtained 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 tug 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',
identify: '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 */,
}
}
]
}
It’s essential to present JSON for VS Code to configure issues, so I additionally obtained AI to create a construct script that converts the above format right into a .json file.
You will discover the construct script and every part I used within the GitHub Repo.
Debugging regionally
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 growth, 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 should utilize F5 (Home windows) or Fn + F5 (Mac) and a brand new editor window will pop up — on this new window, you’ll be able to change the theme to your customized theme.
Recognizing a window that makes use of the extension host is sort of easy as a result of:
- For those who change your theme, that window might 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 part has been a blur at this level, so I can’t keep in mind if it’s essential embody the next into your bundle.json file for theme switching to work within the extension host. In that case, embody it:
{
"contributes": {
"themes": [
{
"label": "Your Theme Name",
"uiTheme": "vs-dark",
"path": ".json"
}
]
}
}
Understanding TextMate scopes
At first, I copy-pasted pictures and tried to get AI to regulate numerous tokens to the colours I selected. But it surely obtained irritating fairly shortly.
Both:
- the AI obtained the textmate scope mistaken, or
- it was overwritten by one thing else.
I couldn’t inform. However fortunately you’ll be able to debug the TextMate scopes simply with a “Developer: Inspector Editor Tokens and Scopes” command.

While you’re on this mode, you’ll be able to click on on any textual content and a window will pop up. This comprises all the knowledge it’s essential regulate TextMate scopes.

Right here’s find out how to learn what’s happening:
- Foreground: Tells you the present energetic scope. On this case, the energetic scope is
variable. - TextMate scopes: Tells you what are the out there TextMate scopes you should utilize for this particular token.
TextMate scopes work in an attention-grabbing means. I discovered the next by experimenting, so it won’t be 100% correct:
- You should use any a part of the out there scopes.
variable,variable.prop, andvariable.prop.cssall work. - You possibly can 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 possibly can 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 crucial matter when making a theme. There’s no level having the theme if syntax highlighting doesn’t help 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 onerous to inform them aside.
- If every part is highlighted, nothing is highlighted.
- If every part is vital, 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 had been:
- How do I information my eyes?
- What are vital parts that I’ve to see/know?
- What parts are much less vital?
With that, I started working:
Featuresandstrategieshad been vital in order that they needed to be sturdy, so I usedcyanwhich is the strongest colour in my palette.- The
exportkey phrase can be vital because it signifies an export! Key phraseslikeimportandoperatemay be fairly muted, sopurpleit’s.Stringsmay beinexperienced— cos they appear fairly pleasing in a listing of textual content inside a JSON file.

I performed round with the remainder of the colours a little bit, however I finally settled with the next:
Constantsareorangeas a result of it’s kinda straightforward 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 pink as a result of they’re kinda vital — and pink is simpler to learn that cyan.Attributesarepurplefor a similar purpose askey phrases.Partsareorangeas a result of they must be totally different fromTags.- Bonus factors:
TagsandPartsare associated — sopinkandorangefeels excellent right here.

And, lastly, CSS syntax highlighting. Nearly every part 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.Propertymay beinexperiencedas a result of blue is just too uninteresting on this context — andinexperiencedis sweet on the eyes when contrasted with different highly effective colours.

It’s a pity that syntax highlighting for nested courses goes a little bit 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 straightforward 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 stay replace!
Wrapping up
An important I factor I realized throughout this course of is to drift. One opening can result in one other, then one other, and one thing what appears “unimaginable” can develop into “Oh, it’s performed?” in a matter of hours.
I name my theme Twilight Cosmos (AI helped with the naming). You will discover it on:
How did I publish my extension? That’s the topic of a short 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 performed. Be happy to recommend edits to enhance this theme too!
Lastly, join my electronic mail e-newsletter for those who’re curious about listening to my creation adventures. 🙂
That’s it. Thanks for studying and I hope you had a blast!









