Ides Editors

VS Code Theme Customization and Creation

How to customize VS Code's appearance with token colors, workbench theming, and semantic highlighting, plus building and publishing your own theme extension.

VS Code Theme Customization and Creation

Developers spend 8+ hours a day staring at their editor. The colors matter more than aesthetics — they affect readability, eye strain, and how quickly you can scan code. The default themes work, but customizing them to match your preferences (or building your own from scratch) gives you an editor that feels genuinely yours.

I have built custom themes for teams and personal use. The theming system is powerful once you understand the two layers: workbench colors (UI chrome) and token colors (syntax highlighting). This guide covers both, from simple tweaks to publishing a complete theme.

Prerequisites

  • VS Code installed
  • Basic understanding of JSON
  • Familiarity with CSS color formats (hex, RGB, HSL)
  • A color picker tool is helpful but not required

Understanding VS Code's Theme System

VS Code themes have two independent layers:

  1. Workbench colors — the UI itself: sidebar, title bar, tabs, status bar, panels, borders
  2. Token colors — syntax highlighting: keywords, strings, comments, functions, variables

You can customize both without creating a full theme extension, directly in your settings.json.

Quick Customizations in Settings

Overriding Workbench Colors

// settings.json
{
  "workbench.colorCustomizations": {
    // Title bar
    "titleBar.activeBackground": "#1a1a2e",
    "titleBar.activeForeground": "#e0e0e0",

    // Sidebar
    "sideBar.background": "#16213e",
    "sideBar.foreground": "#d4d4d4",
    "sideBarTitle.foreground": "#4fc3f7",

    // Editor
    "editor.background": "#0f0f23",
    "editor.foreground": "#cccccc",
    "editor.lineHighlightBackground": "#1a1a3e",
    "editor.selectionBackground": "#264f78",

    // Status bar
    "statusBar.background": "#0a3d62",
    "statusBar.foreground": "#ffffff",
    "statusBar.debuggingBackground": "#c0392b",

    // Activity bar (left icon bar)
    "activityBar.background": "#0f0f23",
    "activityBar.foreground": "#4fc3f7",

    // Tab bar
    "tab.activeBackground": "#1a1a2e",
    "tab.inactiveBackground": "#0f0f23",
    "tab.activeBorderTop": "#4fc3f7",

    // Terminal
    "terminal.background": "#0f0f23",
    "terminal.foreground": "#cccccc",
    "terminal.ansiGreen": "#4ec9b0",
    "terminal.ansiRed": "#f44747",
    "terminal.ansiYellow": "#dcdcaa",
    "terminal.ansiBlue": "#569cd6",

    // Git decorations
    "gitDecoration.modifiedResourceForeground": "#e2c08d",
    "gitDecoration.untrackedResourceForeground": "#73c991",
    "gitDecoration.deletedResourceForeground": "#c74e39",

    // Minimap
    "minimap.background": "#0f0f23",

    // Brackets
    "editorBracketHighlight.foreground1": "#ffd700",
    "editorBracketHighlight.foreground2": "#da70d6",
    "editorBracketHighlight.foreground3": "#179fff"
  }
}

Overriding Token Colors (Syntax Highlighting)

{
  "editor.tokenColorCustomizations": {
    // Global overrides (apply to all themes)
    "comments": "#6a9955",
    "strings": "#ce9178",
    "keywords": "#569cd6",
    "functions": "#dcdcaa",
    "variables": "#9cdcfe",
    "numbers": "#b5cea8",
    "types": "#4ec9b0",

    // TextMate scope overrides for fine control
    "textMateRules": [
      {
        "scope": "comment",
        "settings": {
          "foreground": "#6a9955",
          "fontStyle": "italic"
        }
      },
      {
        "scope": "keyword.control",
        "settings": {
          "foreground": "#c586c0"
        }
      },
      {
        "scope": "entity.name.function",
        "settings": {
          "foreground": "#dcdcaa",
          "fontStyle": "bold"
        }
      },
      {
        "scope": "variable.parameter",
        "settings": {
          "foreground": "#9cdcfe",
          "fontStyle": "italic"
        }
      },
      {
        "scope": "string.quoted",
        "settings": {
          "foreground": "#ce9178"
        }
      },
      {
        "scope": "constant.numeric",
        "settings": {
          "foreground": "#b5cea8"
        }
      },
      {
        "scope": "storage.type",
        "settings": {
          "foreground": "#569cd6"
        }
      },
      {
        "scope": "entity.name.tag",
        "settings": {
          "foreground": "#569cd6"
        }
      },
      {
        "scope": "entity.other.attribute-name",
        "settings": {
          "foreground": "#9cdcfe",
          "fontStyle": "italic"
        }
      },
      {
        "scope": "support.function",
        "settings": {
          "foreground": "#dcdcaa"
        }
      },
      {
        "scope": "punctuation.definition.tag",
        "settings": {
          "foreground": "#808080"
        }
      }
    ]
  }
}

Per-Theme Overrides

Apply customizations only to specific themes:

{
  "workbench.colorCustomizations": {
    "[Dark+ (default dark)]": {
      "editor.background": "#1a1a1a",
      "statusBar.background": "#333333"
    },
    "[Monokai]": {
      "editor.background": "#272822",
      "statusBar.background": "#414339"
    }
  },
  "editor.tokenColorCustomizations": {
    "[Dark+ (default dark)]": {
      "comments": "#608b4e"
    }
  }
}

Finding Token Scopes

To customize a specific syntax element, you need to know its TextMate scope. Use the Scope Inspector:

  1. Open a file with the code you want to style
  2. Ctrl+Shift+P → "Developer: Inspect Editor Tokens and Scopes"
  3. Click on any token to see its scope

The inspector shows:

Token: function
Scope: storage.type.function.js
Foreground: #569cd6 (from Dark+ theme)

Use the most specific scope for targeted styling, or a broader scope for wider changes:

// Specific: only JavaScript function keyword
"storage.type.function.js"

// Broader: all storage types (var, function, class)
"storage.type"

// Broadest: all storage tokens
"storage"

Semantic Highlighting

VS Code 1.44+ supports semantic highlighting, which is language-aware. Unlike TextMate scopes (which are pattern-based), semantic tokens come from the language server and understand the actual code structure.

{
  "editor.semanticTokenColorCustomizations": {
    "enabled": true,
    "rules": {
      "parameter": {
        "foreground": "#9cdcfe",
        "fontStyle": "italic"
      },
      "property.declaration": {
        "foreground": "#4fc1ff"
      },
      "function.declaration": {
        "foreground": "#dcdcaa",
        "fontStyle": "bold"
      },
      "variable.readonly": {
        "foreground": "#4fc1ff"
      },
      "type": {
        "foreground": "#4ec9b0"
      },
      "enum": {
        "foreground": "#4ec9b0"
      },
      "interface": {
        "foreground": "#4ec9b0",
        "fontStyle": "italic"
      },
      "namespace": {
        "foreground": "#4ec9b0"
      }
    }
  }
}

Semantic tokens override TextMate tokens when both exist. The language server provides richer information — it knows the difference between a function declaration and a function call, between a parameter and a local variable.

Building a Theme Extension

Scaffolding

npm install -g yo generator-code
yo code

Choose:

? What type of extension? New Color Theme
? Do you want to import or convert an existing theme? No, start fresh
? What's the name? Midnight Ocean
? What's the identifier? midnight-ocean
? What's the description? A dark theme with ocean-inspired colors
? What's the base theme? Dark

Theme File Structure

midnight-ocean/
  themes/
    midnight-ocean-color-theme.json    # The theme definition
  package.json                          # Extension manifest
  README.md
  CHANGELOG.md
  icon.png                             # Theme icon (128x128)

Package.json for Themes

{
  "name": "midnight-ocean",
  "displayName": "Midnight Ocean",
  "description": "A dark theme with ocean-inspired colors",
  "version": "1.0.0",
  "engines": { "vscode": "^1.80.0" },
  "categories": ["Themes"],
  "contributes": {
    "themes": [
      {
        "label": "Midnight Ocean",
        "uiTheme": "vs-dark",
        "path": "./themes/midnight-ocean-color-theme.json"
      },
      {
        "label": "Midnight Ocean Soft",
        "uiTheme": "vs-dark",
        "path": "./themes/midnight-ocean-soft-color-theme.json"
      }
    ]
  },
  "publisher": "your-publisher-name",
  "icon": "icon.png",
  "repository": {
    "type": "git",
    "url": "https://github.com/yourname/midnight-ocean"
  }
}

The uiTheme value determines the base:

  • vs — light theme
  • vs-dark — dark theme
  • hc-black — high contrast dark
  • hc-light — high contrast light

Complete Theme Definition

{
  "name": "Midnight Ocean",
  "type": "dark",
  "semanticHighlighting": true,
  "colors": {
    // Editor
    "editor.background": "#0b1929",
    "editor.foreground": "#d4d4d4",
    "editor.lineHighlightBackground": "#0d2137",
    "editor.selectionBackground": "#1b4b73",
    "editor.wordHighlightBackground": "#1b4b7350",
    "editor.findMatchBackground": "#515c6a",
    "editor.findMatchHighlightBackground": "#ea5c0050",
    "editorCursor.foreground": "#4fc3f7",
    "editorWhitespace.foreground": "#1e3a5c",
    "editorIndentGuide.background1": "#1e3a5c",
    "editorIndentGuide.activeBackground1": "#3a6b94",
    "editorLineNumber.foreground": "#2a4f6f",
    "editorLineNumber.activeForeground": "#4fc3f7",
    "editorRuler.foreground": "#1e3a5c",

    // Brackets
    "editorBracketMatch.background": "#1b4b7350",
    "editorBracketMatch.border": "#4fc3f7",
    "editorBracketHighlight.foreground1": "#ffd700",
    "editorBracketHighlight.foreground2": "#da70d6",
    "editorBracketHighlight.foreground3": "#4fc3f7",

    // Gutter
    "editorGutter.addedBackground": "#4ec9b0",
    "editorGutter.modifiedBackground": "#e2c08d",
    "editorGutter.deletedBackground": "#f44747",

    // Sidebar
    "sideBar.background": "#091422",
    "sideBar.foreground": "#8badc4",
    "sideBarTitle.foreground": "#4fc3f7",
    "sideBarSectionHeader.background": "#0b1929",

    // Activity bar
    "activityBar.background": "#071018",
    "activityBar.foreground": "#4fc3f7",
    "activityBar.inactiveForeground": "#3a6b94",
    "activityBarBadge.background": "#007acc",

    // Title bar
    "titleBar.activeBackground": "#071018",
    "titleBar.activeForeground": "#8badc4",
    "titleBar.inactiveBackground": "#071018",

    // Status bar
    "statusBar.background": "#0a2744",
    "statusBar.foreground": "#8badc4",
    "statusBar.debuggingBackground": "#c0392b",
    "statusBar.noFolderBackground": "#1e3a5c",

    // Tabs
    "tab.activeBackground": "#0b1929",
    "tab.activeForeground": "#ffffff",
    "tab.inactiveBackground": "#091422",
    "tab.inactiveForeground": "#5a8ba8",
    "tab.activeBorderTop": "#4fc3f7",
    "tab.border": "#091422",

    // Terminal
    "terminal.background": "#0b1929",
    "terminal.foreground": "#d4d4d4",
    "terminal.ansiBlack": "#0b1929",
    "terminal.ansiRed": "#f44747",
    "terminal.ansiGreen": "#4ec9b0",
    "terminal.ansiYellow": "#dcdcaa",
    "terminal.ansiBlue": "#569cd6",
    "terminal.ansiMagenta": "#c586c0",
    "terminal.ansiCyan": "#4fc3f7",
    "terminal.ansiWhite": "#d4d4d4",

    // Input
    "input.background": "#0d2137",
    "input.border": "#1e3a5c",
    "input.foreground": "#d4d4d4",
    "input.placeholderForeground": "#3a6b94",
    "focusBorder": "#4fc3f7",

    // Dropdown
    "dropdown.background": "#0d2137",
    "dropdown.border": "#1e3a5c",

    // Lists
    "list.activeSelectionBackground": "#1b4b73",
    "list.hoverBackground": "#0d2137",
    "list.inactiveSelectionBackground": "#0d2137",

    // Git
    "gitDecoration.modifiedResourceForeground": "#e2c08d",
    "gitDecoration.untrackedResourceForeground": "#4ec9b0",
    "gitDecoration.deletedResourceForeground": "#f44747",

    // Peek view
    "peekView.border": "#4fc3f7",
    "peekViewEditor.background": "#091422",
    "peekViewResult.background": "#091422",
    "peekViewTitle.background": "#0b1929"
  },
  "tokenColors": [
    {
      "scope": ["comment", "punctuation.definition.comment"],
      "settings": {
        "foreground": "#3a6b94",
        "fontStyle": "italic"
      }
    },
    {
      "scope": ["string", "string.quoted"],
      "settings": {
        "foreground": "#ce9178"
      }
    },
    {
      "scope": "string.regexp",
      "settings": {
        "foreground": "#d16969"
      }
    },
    {
      "scope": ["constant.numeric", "constant.language"],
      "settings": {
        "foreground": "#b5cea8"
      }
    },
    {
      "scope": ["keyword", "storage.type", "storage.modifier"],
      "settings": {
        "foreground": "#569cd6"
      }
    },
    {
      "scope": "keyword.control",
      "settings": {
        "foreground": "#c586c0"
      }
    },
    {
      "scope": "keyword.operator",
      "settings": {
        "foreground": "#d4d4d4"
      }
    },
    {
      "scope": ["entity.name.function", "support.function"],
      "settings": {
        "foreground": "#dcdcaa"
      }
    },
    {
      "scope": "entity.name.type",
      "settings": {
        "foreground": "#4ec9b0"
      }
    },
    {
      "scope": "variable",
      "settings": {
        "foreground": "#9cdcfe"
      }
    },
    {
      "scope": "variable.parameter",
      "settings": {
        "foreground": "#9cdcfe",
        "fontStyle": "italic"
      }
    },
    {
      "scope": "entity.name.tag",
      "settings": {
        "foreground": "#569cd6"
      }
    },
    {
      "scope": "entity.other.attribute-name",
      "settings": {
        "foreground": "#9cdcfe",
        "fontStyle": "italic"
      }
    },
    {
      "scope": "punctuation",
      "settings": {
        "foreground": "#808080"
      }
    },
    {
      "scope": "meta.embedded",
      "settings": {
        "foreground": "#d4d4d4"
      }
    },
    {
      "scope": "markup.heading",
      "settings": {
        "foreground": "#569cd6",
        "fontStyle": "bold"
      }
    },
    {
      "scope": "markup.bold",
      "settings": {
        "fontStyle": "bold"
      }
    },
    {
      "scope": "markup.italic",
      "settings": {
        "fontStyle": "italic"
      }
    },
    {
      "scope": "markup.inline.raw",
      "settings": {
        "foreground": "#ce9178"
      }
    }
  ],
  "semanticTokenColors": {
    "parameter": {
      "foreground": "#9cdcfe",
      "italic": true
    },
    "property.declaration": "#4fc1ff",
    "function.declaration": {
      "foreground": "#dcdcaa",
      "bold": true
    },
    "variable.readonly": "#4fc1ff",
    "type": "#4ec9b0",
    "interface": {
      "foreground": "#4ec9b0",
      "italic": true
    }
  }
}

Testing Your Theme

Press F5 to launch the Extension Development Host with your theme active. Make changes to the theme JSON file and the preview updates live.

Use the Developer Tools (Ctrl+Shift+I) to inspect elements and find the correct color keys.

Publishing

npm install -g @vscode/vsce
vsce login your-publisher
vsce package     # Creates .vsix file
vsce publish     # Publishes to marketplace

Common Issues and Troubleshooting

Color customization has no effect

The color key name is wrong. VS Code silently ignores unknown keys:

Fix: Use VS Code's IntelliSense in settings.json — it autocompletes valid color keys. Or check the Theme Color Reference.

Token colors do not apply to expected tokens

The TextMate scope you are targeting does not match the actual scope:

Fix: Use "Developer: Inspect Editor Tokens and Scopes" to find the exact scope string for the token you want to style.

Theme looks different across languages

Different language grammars use different scopes for similar constructs:

Fix: Target broader scopes (e.g., keyword instead of keyword.control.js) for consistency, or add language-specific rules for each language you support.

Semantic highlighting overrides your token colors

When semantic highlighting is enabled, it takes precedence over TextMate rules:

Fix: Define semanticTokenColors in your theme to control semantic highlighting colors explicitly. Or set "editor.semanticHighlighting.enabled": false to disable it.

Best Practices

  • Start with an existing theme and modify it. Do not design from a blank canvas. Fork a theme you like and adjust colors incrementally.
  • Use a consistent color palette. Pick 8-10 colors and use them consistently. Random colors make code harder to scan, not easier.
  • Test with real code in multiple languages. A theme that looks great with JavaScript may look terrible with Python or HTML. Test broadly.
  • Ensure sufficient contrast. WCAG AA requires 4.5:1 contrast ratio for text. Use a contrast checker tool to verify readability.
  • Use semantic highlighting. It provides richer information than TextMate scopes. Set "semanticHighlighting": true in your theme.
  • Provide both a standard and soft variant. Some developers prefer higher contrast, others prefer muted colors. Two variants cover both.
  • Include a screenshot in your README. Users choose themes visually. Show code samples in JavaScript, Python, HTML, and Markdown.

References

Powered by Contentful