You’d think that publishing a VS Code extension is an easy process, but it’s not. (Maybe I’m used to the ease of publishing npm packages and take registries for granted.)
Anyway, you have to publish your theme in two places:
You might also want to publish to npm for others to use your theme easily for other contexts — like syntax highlighting via Shiki.
Preparing your theme
When you name your theme, you cannot put it under a scope like @scope/theme-name. Doing so will prevent you from publishing to Open VSX.
So, make sure your theme name is unscoped. (The theme word is optional):
{
"name": "twilight-cosmos-theme",
}
To include an icon for your theme, you need a 128px square image file that can be accessible within your project. Put this under the icon property to point to the file:
{
"icon": "path/to/icon.png",
}
Next, you want to ensure that you have a contributes key in your package.json file. VS Code and other text editors search for this to find themes.
Microsoft lets you publish to Visual Studio Marketplace via vsce if you have a personal access token from an Azure DevOps account.
Unfortunately, while creating this article, I encountered several problems setting up my Azure Devops account so I had to publish my extension via the manual route.
I’ll talk about both routes here.
Before publishing, you need to have a Visual Studio Marketplace account. So, sign up for one if you don’t have it yet.
Then do the following:
Click on Publish Extension.
Create a publisher account.
This step is needed for publishing both via vsce and the manual route.
Publishing via VSCE
For this to work, you need a Azure DevOps account. When you have that, you can create a Personal Access Token with these steps.
Note: It’s kinda irritating that you can’t have an lifetime access token with Azure DevOps. The maximum expiry is about one year later.
Also note: I had immense trouble creating my Azure DevOps account when I tried this — the back end kept hanging and I couldn’t find the right page, even when I copy-pasted the URL! Anyway, don’t be alarmed if this happens to you. You might just need to wait 1-2 days before you try again. It will work, eventually.
Once you have the personal access token, the rest of the steps is pretty straightforward.
First, you login to VSCE with your publisher ID that you created in Visual Studio Marketplace. (Insert the publisher ID, not the user ID!).
npx vsce login <publisher_id>
You’ll have to insert the access token when it asks you to. Then, run the next command to publish to the marketplace:
npx vsce publish
And you’re done!
Publishing manually
You’ll have to follow this route if you had problems with the personal access token like I did. Thankfully, it’s pretty straightforward as well. You can go to Visual Studio Marketplace and do the following:
Click on Publish Extensions.
Click New Extension.
Use the vsce package command to package your extension as a visx file.
Drag and drop the packaged visx file to upload your extension.
That’s it!
Getting verified on Visual Studio Code
If this is your first extension, you can only get “verified” on the Visual Studio Marketplace if your extension is at least six months old. So, if you want to get verified, set a reminder in six months and visit this page for more information.
Publishing to Open VSX
Thanks to Claude, I understood VS Code uses the Visual Studio Marketplace, but other text editors, like Cursor, use Open VSX.
Publishing to Open VSX is a bit more complex. You have to:
Then, finally, run npx ovsx publish to publish your package.
Likewise, ovsx will ask you for a personal access token when you try to publish for the first time. Thankfully, ovsx seems to have a lifetime access token seems so we don’t have to worry about it expiring.
Claiming the publisher namespace
This is essentially getting “verified” with Open VSX, but Open VSX calls it “claiming” the publisher namespace to get verified. Without harping on the language too much — this process takes a bit of to-and-fro but can be done now (instead of six months later).
Once you have created a publisher namespace, you’ll see a glaring warning sign:
Include your GitHub repository (if you make it publicly available).
Offer to give access temporarily to your GitHub repository (if it’s private).
And someone will handle the rest.
The team at Eclipse Foundation seems to be pretty responsive, so I wouldn’t worry about communication breakdown here.
Including images for your theme
It makes sense to include images to showcase your theme in the Readme.md file. Doing so allows users to get a sense of your theme colors before deciding whether they want to download it.
Unfortunately, both VS Marketplace and Open VSX do not allow you to use relative URLs — images will be broken if you use relative links from your repository — so you have to link to an absolute URL instead.
The best place to link to is the GitHub repository, as long as it is set to public access.
Years ago, when I read Sarah Drasner’s article on creating a VS Code theme, I silently thought to myself, That’s a lot of work… I’m never going to make a theme…
But lo and behold, I went ahead and made one — and it took less than six hours to get most of the theme working, then a day or two to polish up my final tweaks.
In this article, I want to you walk you through my process of creating this theme — along with the actual steps I took to create it.
I think talking about the process is powerful because I went from Nah, too much work to Oh, I can do it to It’s done..? all within a matter of hours. (The rest is simply time spent polishing).
I never wanted to make a VS Code theme…
I was in the middle of redesigning my website. I’ve been rocking a super duper old design that I’ve wanted to change for years — and I finally started moving.
I used Dracula Theme for code snippets in my old design and it worked since Dracula was the only thing that provided a splash of color in my otherwise stark design.
But it didn’t work well with my new site design.
All I wanted to do was to improve syntax highlighting for the code blocks so they’re more aligned with the rest of the site.
That was the beginning of everything.
Shiki CSS variable theming made it simple
I use Astro for my website. Shiki is a syntax highlighter that is built into Astro by default.
With some quick research, I realized Shiki allows you to create themes with CSS variables — and there are only a handful of colors we need to choose.
That doesn’t sound too complicated, so I got AI to help flesh out a Shiki theme based on the CSS variables. Here’s the CSS and JavaScript you need if you’re using Astro as well:
I did a quick experiment with the colors I had already used for my website and compared it to various popular themes, like Dracula, Sarah’s Night Owl, and Moonlight 2.
This gave me the confidence to push my own theme a little further — because the syntax highlighting was shaping up in the right direction.
But, to push this further, I had to ditch CSS variable theming and dive into TextMate tokens. It was essential because certain code blocks looked absolutely horrendous and TextMate tokens provide more granular control of how and what gets color.
This is where the “hard” part begins.
Getting AI to help with TextMate scopes
Thankfully, AI is here to help. If AI wasn’t here, I might have just given up at this point.
Here’s what I got my AI to do:
I said I wanted to make a custom theme.
I told it to create a scaffold for me.
I asked it to look for Moonlight 2’s theme files as a reference and create the TextMate scope tokens based on that.
I got it to consolidate the colors used into semantic keywords like foreground, background, keyword — like the Shiki CSS variable theme.
And I asked it to pull all of the colors into a color object so I can have a palette object that includes only the semantic names.
Here’s roughly what it created:
const colors = {
purple: '...',
blue: '...',
// ...
}
const palette = {
foreground: '...',
background: '...',
// ...
}
export default {
colors: {
// Used for theming the text editor
},
displayName: 'Display Name of your Theme',
name: 'your-theme-name',
tokenColors: [
{
name: 'Scope name (optional)',
scope: [/*scopes used*/],
settings: {
foreground: /* change color */,
background: /* background of the text */,
fontStyle: /* normal, bold or italic */,
}
}
]
}
You need to provide JSON for VS Code to configure things, so I also got AI to create a build script that converts the above format into a .json file.
You can find the build script and everything I used in the GitHub Repo.
Debugging locally
It was impossible to debug syntax highlighting on my website because I had to manually restart the server whenever I changed a variable.
So, I asked AI for a suggestion.
It said that I can use VS Code’s Extension Host for local development, then proceeded to created a .vscode/launch.json file with the following contents:
To run this, you can use F5 (Windows) or Fn + F5 (Mac) and a new editor window will pop up — in this new window, you can change the theme to your custom theme.
Spotting a window that uses the extension host is quite simple because:
If you change your theme, that window will be a different theme compared to your other opened text editors.
The Extension Host keyword is prominent in the title.
Now, everything has been a blur at this point, so I can’t remember if you need to include the following into your package.json file for theme switching to work in the extension host. If so, include it:
At first, I copy-pasted images and tried to get AI to adjust various tokens to the colors I chose. But it got frustrating quite quickly.
Either:
the AI got the textmate scope wrong, or
it was overwritten by something else.
I couldn’t tell. But thankfully you can debug the TextMate scopes easily with a “Developer: Inspector Editor Tokens and Scopes” command.
When you’re in this mode, you can click on any text and a window will pop up. This contains all the information you need to adjust TextMate scopes.
Here’s how to read what’s going on:
Foreground: Tells you the current active scope. In this case, the active scope is variable.
TextMate scopes: Tells you what are the available TextMate scopes you can use for this specific token.
TextMate scopes work in an interesting way. I figured out the following by experimenting, so it might not be 100% accurate:
You can use any part of the available scopes.variable, variable.prop, and variable.prop.css all work.
You can increase specificity by stating more properties.variable.prop.css > variable.prop > variable in terms of specificity.
The higher scope is more specific than the lower one.variable > meta.function.misc.css.
You can other scopes with them like CSS selectors if you need to overwrite a higher scope. meta.function variable > variable
How I chose colors for the theme
This is the most important topic when creating a theme. There’s no point having the theme if syntax highlighting doesn’t support the developer in reading code.
Essentially, the principles that I took away from both articles are:
We want highlights to stand out.
Colors will look very similar to each other if you make use the same lightness and chroma, and it’ll be hard to tell them apart.
If everything is highlighted, nothing is highlighted.
If everything is important, nothing is.
Basically, we’re talking about the principle of contrast when designing. Since I’m already designing for someone to read, the very next thoughts that came were:
How do I guide my eyes?
What are important elements that I have to see/know?
What elements are less important?
With that, I began working:
Functions and methods were important so they had to be strong, so I used cyan which is the strongest color in my palette.
The export keyword is also important since it signifies an export!
Keywords like import and function can be rather muted, so purple it is.
Strings can be green — cos they seem rather pleasing in a list of text within a JSON file.
If text wasn’t green…this might be hard to look at.
I played around with the rest of the colors a little, but I eventually settled with the following:
Constants are orange because it’s kinda easy to spot them
Variables are white-ish because that’s the bulk of the text — adding colors to them creates the “Christmas Lights Diarrhea” effect Tonsky mentioned.
Properties are blue because they’re like workhorses that needs color differentiation, but not enough to draw too much attention.
Then I moved onto HTML/Astro/Svelte:
Tags are red because they’re kinda important — and red is easier to read that cyan.
Attributes are purple for the same reason as keywords.
Components are orange because they need to be different from Tags.
Bonus points: Tags and Components are related — so red and orange feels just right here.
And, finally, CSS syntax highlighting. Almost everything seemed right at this point, except that:
CSS Functions should be cyan like that in JS.
Punctuation should be muted so we can easily differentiate the -- from the rest of the text.
Property can be green because blue is too dull in this context — and green is nice on the eyes when contrasted with other powerful colors.
It’s a pity that syntax highlighting for nested classes goes a little bit haywire (they’re green, but they should be orange), but there’s nothing much I can do about it.
Debugging colors
VS Code is built on Electron, so it’s easy to debug and test colors. What I had to do was fire up devtools, inspect the color I wanted to change, and change them directly to get a live update!
Wrapping up
The most important I thing I learned during this process is to go with the flow. One opening can lead to another, then another, and something what seems “impossible” can become “Oh, it’s done?” in a matter of hours.
I call my theme Twilight Cosmos (AI helped with the naming). You can find it on: