Beautiful light and dark themes, generated from a shared color palette, for Visual Studio Code, Cursor, and Shiki. Built first for @pierre/diffs, and shared with the community by The Pierre Computer Company.
Install the Pierre theme pack from your editor's extension marketplace:
Then open your editor and select "Pierre Dark" or "Pierre Light" in settings or the command palette.
Create custom themes for use with @pierre/diffs (plus Shiki and Visual Studio
Code based editors) by forking our open-source
Pierre Theme repository.
The Pierre themes are bundled with @pierre/diffs as pierre-dark and
pierre-light, so you can use them without any additional setup.
The fastest way to create a custom theme is to fork the Pierre Theme repository, modify our color palette, and regenerate the themes. This repository uses TypeScript to generate theme JSON files, giving you type safety and the ability to generate multiple theme files—for various editors, too—from a single color palette.
git clone https://github.com/YOUR_USERNAME/theme.gitnpm installnpm start (watches for changes and rebuilds)The Pierre Theme repository is organized as follows:
1theme/2├── src/3│ ├── build.ts # Build script entry point4│ ├── color-p3.ts # Display P3 color definitions5│ ├── palette.ts # Color definitions (edit this!)6│ ├── package-vsix.ts # Generates VS Code/Cursor extension7│ └── theme.ts # Token color mappings8├── themes/ # Generated theme JSON files9│ ├── pierre-dark.json10│ ├── pierre-light.json11│ ├── pierre-dark-vibrant.json12│ └── pierre-light-vibrant.json13└── package.json # Update with your detailsThe src/palette.ts file is the single source of truth for all colors in your
theme. We take each color from the palette and provide several tints and shades
for each, like 100, 200, 300, etc. We ship with all our tints and shades, so
this is overkill for most themes, but it's sometimes helpful to have full access
to your entire brand or product palette.
1const gray = {2 "020":"#fbfbfb",3 "040":"#f9f9f9",4 "060":"#f8f8f8",5 "080":"#f2f2f3",6 "100":"#eeeeef",7 "200":"#dbdbdd",8 "300":"#c6c6c8",9 "400":"#adadb1",10 "500":"#8E8E95",11 "600":"#84848A",12 "700":"#79797F",13 "800":"#6C6C71",14 "900":"#4A4A4E",15 "920":"#424245",16 "940":"#39393c",17 "960":"#2e2e30",18 "980":"#1F1F21",19 "1000":"#141415",20 "1020":"#0B0B0C",21 "1040":"#070707"22};23
24const red = {25 "050":"#ffedea",26 "100":"#ffdbd6",27 "200":"#ffb7ae",28 "300":"#ff9187",29 "400":"#ff6762",30 "500":"#ff2e3f",31 "600":"#d52c36",32 "700":"#ad292e",33 "800":"#862425",34 "900":"#611e1d",35 "950":"#3e1715"36};37
38// Additional color scales: orange, yellow, green, mint,39// teal, cyan, blue, indigo, purple, pink, brown40// ... (same pattern as above)After defining your color scales, you define role types that map colors to semantic purposes—backgrounds, foregrounds, borders, accents, states, syntax highlighting, and ANSI terminal colors:
1export type Roles = {2 bg: {3 editor: string; // main editor background4 window: string; // sidebar, activity bar, status bar5 inset: string; // inputs, dropdowns6 elevated: string; // panels, hover backgrounds7 };8 fg: {9 base: string;10 fg1: string;11 fg2: string;12 fg3: string;13 fg4: string;14 };15 border: {16 window: string;17 editor: string;18 indentGuide: string;19 indentGuideActive: string;20 inset: string;21 elevated: string;22 };23 accent: {24 primary: string;25 link: string;26 subtle: string;27 contrastOnAccent: string;28 };29 states: {30 merge: string;31 success: string;32 danger: string;33 warn: string;34 info: string;35 };36 syntax: {37 comment: string;38 string: string;39 number: string;40 keyword: string;41 regexp: string;42 func: string;43 type: string;44 variable: string;45 operator: string;46 punctuation: string;47 constant: string;48 parameter: string;49 namespace: string;50 decorator: string;51 escape: string;52 invalid: string;53 tag: string;54 attribute: string;55 };56 ansi: {57 black: string;58 red: string;59 green: string;60 yellow: string;61 blue: string;62 magenta: string;63 cyan: string;64 white: string;65 brightBlack: string;66 brightRed: string;67 brightGreen: string;68 brightYellow: string;69 brightBlue: string;70 brightMagenta: string;71 brightCyan: string;72 brightWhite: string;73 };74};Then export light and dark theme configurations using your color scales:
1export const light: Roles = {2 bg: {3 editor: "#ffffff",4 window: gray["060"],5 inset: gray["080"],6 elevated: gray["040"]7 },8 fg: {9 base: gray["1040"],10 fg1: gray["900"],11 fg2: gray["800"],12 fg3: gray["600"],13 fg4: gray["500"]14 },15 border: {16 window: gray["100"],17 editor: gray["200"],18 indentGuide: gray["100"],19 indentGuideActive: gray["200"],20 inset: gray["200"],21 elevated: gray["100"]22 },23 accent: {24 primary: blue["500"],25 link: blue["500"],26 subtle: blue["100"],27 contrastOnAccent: "#ffffff"28 },29 states: {30 merge: indigo["500"],31 success: mint["500"],32 danger: red["500"],33 warn: yellow["500"],34 info: cyan["500"]35 },36 syntax: {37 comment: gray["600"],38 string: green["600"],39 number: cyan["600"],40 keyword: pink["500"],41 regexp: teal["600"],42 func: indigo["500"],43 type: purple["500"],44 variable: orange["600"],45 operator: cyan["500"],46 punctuation: gray["700"],47 constant: yellow["600"],48 parameter: gray["700"],49 namespace: yellow["600"],50 decorator: blue["500"],51 escape: cyan["600"],52 invalid: "#ffffff",53 tag: red["600"],54 attribute: mint["600"]55 },56 ansi: {57 black: gray["980"],58 red: red["500"],59 green: green["500"],60 yellow: yellow["500"],61 blue: blue["500"],62 magenta: purple["500"],63 cyan: cyan["500"],64 white: gray["300"],65 brightBlack: gray["980"],66 brightRed: red["500"],67 brightGreen: green["500"],68 brightYellow: yellow["500"],69 brightBlue: blue["500"],70 brightMagenta: purple["500"],71 brightCyan: cyan["500"],72 brightWhite: gray["300"]73 }74};The dark theme follows the same pattern with adjusted values for dark mode. After editing your palette, the build script automatically regenerates all theme variants. Each color in the palette is used consistently across light theme, dark theme, and vibrant variants.
See the full source files on GitHub:
The src/theme.ts file maps your src/palette.ts colors to specific syntax
tokens. This is where you control which colors are used for editor interface
elements and syntax highlighting (keywords, functions,comments, strings, etc).
1import type { Roles } from "./palette";2
3type VSCodeTheme = {4 name: string;5 type: "light" | "dark";6 colors: Record<string, string>;7 tokenColors: any[];8 semanticTokenColors: Record<string, string | { foreground: string; fontStyle?: string }>;9};10
11export function makeTheme(name: string, kind: "light" | "dark", c: Roles): VSCodeTheme {12 return {13 name,14 type: kind,15 colors: {16 // Core editor & text17 "editor.background": c.bg.editor,18 "editor.foreground": c.fg.base,19 "foreground": c.fg.base,20 "focusBorder": c.accent.primary,21 "selection.background": c.accent.subtle,22
23 // Editor chrome24 "editor.selectionBackground": alpha(c.accent.primary, kind === "dark" ? 0.30 : 0.18),25 "editor.lineHighlightBackground": alpha(c.accent.subtle, 0.55),26 "editorCursor.foreground": c.accent.primary,27 "editorLineNumber.foreground": c.fg.fg3,28 "editorLineNumber.activeForeground": c.fg.fg2,29 "editorIndentGuide.background": c.border.indentGuide,30 "editorIndentGuide.activeBackground": c.border.indentGuideActive,31
32 "diffEditor.insertedTextBackground": alpha(c.states.success, kind === "dark" ? 0.1 : 0.2),33 "diffEditor.deletedTextBackground": alpha(c.states.danger, kind === "dark" ? 0.1 : 0.2),34
35 // Sidebar36 "sideBar.background": c.bg.window,37 "sideBar.foreground": c.fg.fg2,38 "sideBar.border": c.border.window,39 "sideBarTitle.foreground": c.fg.base,40 "sideBarSectionHeader.background": c.bg.window,41 "sideBarSectionHeader.foreground": c.fg.fg2,42 "sideBarSectionHeader.border": c.border.window,43
44 // Activity bar45 "activityBar.background": c.bg.window,46 "activityBar.foreground": c.fg.base,47 "activityBar.border": c.border.window,48 "activityBar.activeBorder": c.accent.primary,49 "activityBarBadge.background": c.accent.primary,50 "activityBarBadge.foreground": c.accent.contrastOnAccent,51
52 // ... tabs, panels, status bar, inputs, buttons, git, terminal53
54 },55
56 tokenColors: [57 // COMMENTS58 { scope: ["comment", "punctuation.definition.comment"], settings: { foreground: c.syntax.comment } },59
60 // STRINGS61 { scope: ["string", "constant.other.symbol"], settings: { foreground: c.syntax.string } },62
63 // NUMBERS & CONSTANTS64 { scope: ["constant.numeric", "constant.language.boolean"], settings: { foreground: c.syntax.number } },65
66 // KEYWORDS & STORAGE67 { scope: "keyword", settings: { foreground: c.syntax.keyword } },68 { scope: ["storage", "storage.type", "storage.modifier"], settings: { foreground: c.syntax.keyword } },69
70 // VARIABLES & IDENTIFIERS71 { scope: ["variable", "identifier", "meta.definition.variable"], settings: { foreground: c.syntax.variable } },72 { scope: "variable.parameter", settings: { foreground: c.syntax.parameter } },73
74 // FUNCTIONS & METHODS75 { scope: ["support.function", "entity.name.function"], settings: { foreground: c.syntax.func } },76
77 // TYPES & CLASSES78 { scope: ["support.type", "entity.name.type", "entity.name.class"], settings: { foreground: c.syntax.type } },79
80 // ... operators, punctuation, language-specific rules, CSS, HTML, Markdown, etc.81 ],82
83 semanticTokenColors: {84 comment: c.syntax.comment,85 string: c.syntax.string,86 number: c.syntax.number,87 keyword: c.syntax.keyword,88 variable: c.syntax.variable,89 parameter: c.syntax.parameter,90 function: c.syntax.func,91 type: c.syntax.type,92 class: c.syntax.type,93 namespace: c.syntax.namespace,94 // ...95 }96 };97}See the full source:
src/theme.ts.
For a complete reference of TextMate scopes, see the VS Code syntax highlighting documentation and the TextMate language grammars manual.
package.jsonBefore publishing your theme, update the package.json with your own details.
The VS Code Marketplace uses fields like displayName, description, icon,
repository, and categories for your listing. See the
extension manifest reference
for all available fields.
1{2 "name": "my-custom-theme",3 "displayName": "My Custom Theme",4 "description": "A beautiful theme for VS Code and Shiki",5 "version": "1.0.0",6 "publisher": "your-publisher-id",7 "author": "Your Name",8 "license": "MIT",9 "icon": "images/icon.png",10 "galleryBanner": {11 "color": "#1F1F21",12 "theme": "dark"13 },14 "keywords": ["theme", "color-theme", "dark", "light"],15 "repository": {16 "type": "git",17 "url": "https://github.com/YOUR_USERNAME/my-custom-theme"18 },19 "homepage": "https://github.com/YOUR_USERNAME/my-custom-theme#readme",20 "bugs": {21 "url": "https://github.com/YOUR_USERNAME/my-custom-theme/issues"22 },23 "engines": {24 "vscode": "^1.60.0"25 },26 "categories": ["Themes"],27 "contributes": {28 "themes": [29 {30 "label": "My Theme Dark",31 "uiTheme": "vs-dark",32 "path": "./themes/my-theme-dark.json"33 },34 {35 "label": "My Theme Light",36 "uiTheme": "vs",37 "path": "./themes/my-theme-light.json"38 }39 ]40 }41}The repository includes npm scripts for development:
| Script | Description |
|---|---|
npm start | Watch mode — rebuilds on file changes |
npm run build | Generate theme JSON files in ./themes directory |
npm test | Validate theme structure (runs build first) |
npm run package | Create .vsix file for VS Code/Cursor publishing |
Heads up! Our package script is specifically designed so that you can
publish your theme to npm while also being used for generating the VS Code and
Cursor extensions. We do this by temporarily renaming the package before
generating the VSIX extension file. We replicate this in another way in our
GitHub Action for automatically publishing the
theme
to the VS Code Marketplace and Open VSX on release.
Here's how these all come together. After you make your changes to the color palette, roles, theme file, and package.json:
npm run build to generate the ./themes directory with your theme JSON
files.npm run package to create the .vsix file.npm run test to validate the theme structure.After you've built your theme, you can upload the compiled .vsix file to the
VS Code Marketplace or Open VSX, and use the theme JSON files with
@pierre/diffs.
Once you've built your theme JSON files, you can use them with @pierre/diffs
by registering them before rendering:
1import { registerCustomTheme } from '@pierre/diffs';2
3// Register your theme JSON files before rendering4// The name must match the "name" field in your theme JSON5
6// Option 1: Import from your bundled theme files7registerCustomTheme('my-theme-dark', () => import('./themes/my-theme-dark.json'));8registerCustomTheme('my-theme-light', () => import('./themes/my-theme-light.json'));9
10// Option 2: Fetch from a URL (for CDN-hosted themes)11registerCustomTheme('my-theme-dark', async () => {12 const response = await fetch('/themes/my-theme-dark.json');13 return response.json();14});Then reference your theme in any component:
1import { FileDiff } from '@pierre/diffs/react';2
3export function DiffWithCustomTheme({ fileDiff }) {4 return (5 <FileDiff6 fileDiff={fileDiff}7 options={{8 // Single theme9 theme: 'my-theme-dark',10
11 // Or both variants for automatic light/dark mode12 theme: {13 dark: 'my-theme-dark',14 light: 'my-theme-light',15 },16 }}17 />18 );19}The Pierre Theme repo automatically generates "Vibrant" variants that use the
Display P3 color space for ~25% more color range on compatible displays. These
colors are generated automatically when you run npm run build and come from a
color conversion algorithm.
Display P3 colors are not supported in VS Code or Cursor, so those theme files
aren't generated. Instead, we only generate the .json files for use with
@pierre/diffs and Shiki on the web.
The vibrant variants are generated automatically when you run npm run build.
They use enhanced saturation to push colors into the wider P3 gamut. See the
repo's
DISPLAY-P3.md
for technical details on the color conversion algorithm.
To publish your theme to the VS Code Marketplace or Open VSX:
package.json with your publisher ID and theme metadatanpm run package to create the .vsix fileIf you've forked our theme repository, we automate the publishing process. Our
workflow uses GitHub Actions to build, test, and publish the package to npm and
the theme extension to the VS Code Marketplace and Open VSX on release. You
can adapt this workflow to your own repository—you’ll have to update some
variables and add the appropriate secrets in your repository settings.
For Shiki/web use only, you can skip publishing and simply host your theme JSON files or bundle them with your application.
Thanks again to the Primer team at GitHub for their work on the open source GitHub VS Code themes. We forked their project and heavily modified it for our own themes.
Collectively, our team brings over 150 years of expertise designing, building, and scaling the world's largest distributed systems at Cloudflare, Coinbase, Discord, GitHub, Reddit, Stripe, X, and others.