A code-driven approach to theme your VS Code webview

January 31, 2022

When it comes to theming the webview’s content of your Visual Studio Code extensions, the proposed way by the Visual Studio Code team is to use the provided CSS variables from the current theme. Although, in some cases, you want a bit more control, or make sure it matches what you want to achieve. That is why a code-driven approach is sometimes required.

Let us first focus on the proposed way to theme the webview’s content of your VS Code extension.

If you open the webview its developer tools in VS code, you can spot all these CSS variables in the style attribute of the HTML element.

CSS Variables passed on the HTML element

Webviews can access all the theme colors.

Info You can find an overview of all the color references in the VS Code theme color documentation.

The notation of these variables is as follows; all use the --vscode- prefix, followed by the variable name. The dot (.) gets replaced by a dash (-).

Examples:

1
2
3
4
5
6
7
--vscode-font-weight: normal;
--vscode-font-size: 13px;
--vscode-editor-font-family: "Cascadia Code";
--vscode-editor-font-weight: normal;
--vscode-editor-font-size: 15px;
--vscode-foreground: #f3eff5;
...

Info Here is a list of all the VS Code theme variables: theme variables - gist

In your extension CSS file, you can use these variables as follows:

1
2
3
4
.body {
	background-color: var(--vscode-editor-background);
	color: var(--vscode-editor-foreground);
}

Cool, but what with CSS in JS, theme providers, …?

The default CSS way to theme/style is good enough in many cases, but when using component libraries like MUI, you might want a code-driven to create your themes.

Another advantage of using a code-driven approach is that you can manipulate some theme values, like converting HEX to RGBA to define the alpha value for backgrounds and more.

Getting all CSS variables and their values

As mentioned above, all the CSS variables get defined on the HTML style element. To retrieve these from code, all you need to do is get the value of the style attribute.

One way is to get the values one by one as follows:

1
2
const styles = getComputedStyle(document.querySelector('html'));
const editorBackground = styles.getPropertyValue('--vscode-editor-background')

The above approach might be good if you only need to get a couple of values; the next one might be better if you need more CSS variables or want them all.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const htmlStyle = document.querySelector('html')?.getAttribute('style');
if (!htmlStyle) {
  return;
}

// Retrieve only the VS Code css variables
const codeStyles = htmlStyle.split(';').map(x => x.trim()).filter(s => s.startsWith('--vscode-')).reduce((obj, current) => ({ ...obj, ...toStyleObject(current)}), {});

// Convert the style string to an object of name/value pairs
const toStyleObject = (str: string) => {
  const data = str.split(':');
  let property = data[0];
  const value = data[1];

  property = property.replace('--vscode-', '').split('-').map((valuePart, idx) => {
    if (idx === 0) {
      return valuePart;
    }

    return `${valuePart.charAt(0).toUpperCase()}${valuePart.slice(1)}`;
  }).join('');

  return {
    [property]: value
  };
};

The above code will give you key/value (property/value) pairs and allows you to make use of them in your code. To do so, you can use codeStyles.editorBackground and get the color value.

Theme changes

Wait! What about theme changes? Good question, as when you use the CSS approach, it will all be automatic.

When using the code-driven approach, you will have to observe theme changes. You can use an observer to check the class and attributes on the document body as each time you change the theme, the following attributes change:

  • class: vscode-light, vscode-dark, or vscode-high-contrast
  • data-vscode-theme-kind: same as class
  • data-vscode-theme-name: the name of the theme

An example of the observer looks as follows:

1
2
3
4
5
6
7
const mutationObserver = new MutationObserver((mutationsList, observer) => {
  updateCssVariables();
});

updateCssVariables();

mutationObserver.observe(document.body, { childList: false, attributes: true })

Info In the updateCssVariables method, you can define the same logic from above.

Happy theming your extensions

Comments

comments powered by Disqus