Years in the past, after I learn Sarah Drasner’s article on making a VS Code theme, I silently thought to myself, That’s numerous 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 many 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 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 finished..?
all inside a matter of hours. (The remainder is solely time spent sharpening).
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 shifting.

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 shade in my in any other case stark design.
However it didn’t work effectively with my new website 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 all the things.
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 means that you can 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 bought 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;
shade: 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;
shade: 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 numerous well-liked 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 fitting course.
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 shade.
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 may need simply given up at this level.
Right here’s what I bought my AI to do:
- I stated I wished to make a customized theme.
- I advised 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 bought 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 shade 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 Identify of your Theme',
identify: 'your-theme-name',
tokenColors: [
{
name: 'Scope name (optional)',
scope: [/*scopes used*/],
settings: {
foreground: /* change shade */,
background: /* background of the textual content */,
fontStyle: /* regular, daring or italic */,
}
}
]
}
You have to present JSON for VS Code to configure issues, so I additionally bought AI to create a construct script that converts the above format right into a .json file.
You’ll find the construct script and all the things I used within the GitHub Repo.
Debugging regionally
It was inconceivable 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 need to use 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:
- When you change your theme, that window will probably be a distinct theme in comparison with your different opened textual content editors.
- The Extension Host key phrase is distinguished within the title.

Now, all the things has been a blur at this level, so I can’t keep in mind if you must embrace the next into your bundle.json file for theme switching to work within the extension host. If that’s the case, embrace it:
{
"contributes": {
"themes": [
{
"label": "Your Theme Name",
"uiTheme": "vs-dark",
"path": ".json"
}
]
}
}
Understanding TextMate scopes
At first, I copy-pasted photos and tried to get AI to regulate varied tokens to the colours I selected. However it bought irritating fairly rapidly.
Both:
- the AI bought the textmate scope flawed, 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.

Whenever 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 you must modify TextMate scopes.

Right here’s easy methods 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 accessible 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 accessible scopes.
variable,variable.prop, andvariable.prop.cssall work. - You may enhance 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 may different scopes with them like CSS selectors if you must overwrite a better scope.
meta.operate variable>variable
How I selected colours for the theme
That is an important subject 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:
Basically, the rules 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 all the things is highlighted, nothing is highlighted.
- If all the things 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 components that I’ve to see/know?
- What components are much less essential?
With that, I started working:
Featuresandstrategieshave been essential in order that they needed to be sturdy, so I usedcyanwhich is the strongest shade in my palette.- The
exportkey phrase can be essential because it signifies an export! Key phraseslikeimportandoperatemight be relatively muted, sopurpleit’s.Stringsmight beinexperienced— cos they appear relatively pleasing in an inventory of textual content inside a JSON file.

I performed round with the remainder of the colours a little bit, however I ultimately 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 shade 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 essential — and pink is simpler to learn that cyan.Attributesarepurplefor a similar purpose askey phrases.Partsareorangeas a result of they have to be completely different fromTags.- Bonus factors:
TagsandPartsare associated — sopinkandorangefeels good right here.

And, lastly, CSS syntax highlighting. Virtually all the things appeared proper at this level, besides that:
CSS Featuresmust becyanlike that in JS.Punctuationmust be muted so we will simply differentiate the--from the remainder of the textual content.Propertymight beinexperiencedas a result of blue is just too uninteresting on this context — andinexperiencedis good on the eyes when contrasted with different highly effective colours.

It’s a pity that syntax highlighting for nested lessons 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 take a look at 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
A very powerful I factor I discovered throughout this course of is to flow. One opening can result in one other, then one other, and one thing what appears “inconceivable” can turn into “Oh, it’s finished?” in a matter of hours.
I name my theme Twilight Cosmos (AI helped with the naming). You’ll find 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 finished. Be happy to counsel edits to enhance this theme too!
Lastly, join my electronic mail e-newsletter for those who’re fascinated by listening to my creation adventures. 🙂
That’s it. Thanks for studying and I hope you had a blast!
