A dark and light Neovim colorscheme ported from VS Code's Dark Modern 2026 theme — red keywords, purple functions, teal types and light-blue strings.
Based on D0nw0r/dark2026.nvim, rebuilt as a configurable plugin.
| Target | Repository | |
|---|---|---|
| Neovim | code-2026-theme/nvim | this repo |
| Ghostty | code-2026-theme/ghostty | terminal theme |
| kitty | code-2026-theme/kitty | terminal theme |
| Xcode | code-2026-theme/xcode | editor theme |
| Obsidian | code-2026-theme/obsidian | app theme |
| Yazi | code-2026-theme/yazi | file manager |
Every port shares one palette, so :terminal inside Neovim renders identically to the host
terminal. See Matching your terminal for the values.
- 638 highlight groups — editor UI, legacy syntax, tree-sitter, LSP semantic tokens, diagnostics, diffs, and 28 plugins.
- Transparency for the editor, with floating windows controlled separately.
- Per-token styling — bold/italic/underline for 20 semantic token families.
- Palette overrides, static (
palette) or programmatic (on_colors). - Highlight overrides, static (
highlights) or programmatic (on_highlights). - Per-plugin opt-out for every plugin integration.
- Terminal colors (
vim.g.terminal_color_*) and a lualine theme. - No dependencies. Config errors warn instead of breaking your session.
- Neovim 0.9+
- A true-color terminal (
'termguicolors'is set by the theme)
vim.pack (Neovim 0.12+)
In init.lua:
vim.pack.add {
{ src = 'https://github.com/code-2026-theme/nvim', name = 'code-2026' },
}
require('code-2026').setup {}
vim.cmd.colorscheme 'code-2026'name matters here: the repository is code-2026-theme/nvim, and vim.pack names the
plugin directory after the repository, so without it the theme would be installed as
nvim.
No priority or lazy equivalent is needed — vim.pack.add() installs (on first run) and
adds to 'runtimepath' synchronously, so the plugin is usable on the next line.
To follow a branch, tag or commit instead of the default branch:
vim.pack.add {
{
src = 'https://github.com/code-2026-theme/nvim',
name = 'code-2026',
version = 'main', -- branch, tag, or commit hash
-- version = vim.version.range('1.0'), -- or the greatest matching semver tag
},
}Then:
:lua vim.pack.update()— fetch updates and open a confirmation buffer;:writeto apply,:quitto discard.:lua vim.pack.del({ 'code-2026' })— uninstall, after removing the spec frominit.lua.
See :help vim.pack for the rest.
lazy.nvim
{
'code-2026-theme/nvim',
name = 'code-2026',
lazy = false,
priority = 1000,
opts = {},
config = function(_, opts)
require('code-2026').setup(opts)
vim.cmd.colorscheme 'code-2026'
end,
}packer.nvim
use {
'code-2026-theme/nvim',
as = 'code-2026',
config = function()
require('code-2026').setup {}
vim.cmd.colorscheme 'code-2026'
end,
}setup() is optional — :colorscheme code-2026 alone gives you the defaults. Call setup()
only when you want to change something, and always before the colorscheme command
(calling it afterwards reapplies the theme, which also works).
The complete set of options, with defaults:
require('code-2026').setup {
background = 'dark', -- 'dark' | 'light' | 'auto' | 'sync'
transparent = false, -- clear the editor background
terminal_colors = true, -- set vim.g.terminal_color_0..15
dim_inactive = false, -- darken unfocused windows (NormalNC)
styles = {
comments = { italic = true },
keywords = {},
conditionals = {},
functions = {},
methods = {},
variables = {},
builtins = { italic = true },
parameters = {},
properties = {},
types = {},
strings = {},
numbers = {},
booleans = {},
constants = {},
operators = {},
namespaces = {},
macros = {},
attributes = {},
tags = {},
headings = { bold = true },
floats = 'solid', -- 'solid' | 'transparent' | 'auto'
},
palette = {}, -- static color overrides
on_colors = function(colors) end, -- programmatic color overrides
highlights = {}, -- static highlight overrides
on_highlights = function(highlights, colors) end, -- programmatic highlight overrides
plugins = {}, -- e.g. { telescope = false }
}Unknown option names and invalid styles.floats values produce a warning and are ignored —
a typo will never leave you without a colorscheme.
Selects the light or dark variant.
| Value | Effect |
|---|---|
'dark' (default) |
Always use the dark palette |
'light' |
Always use the light palette |
'auto' |
Detect the OS color mode once at setup() and stay fixed |
'sync' |
Detect at setup() and re-apply whenever the OS color mode changes |
'auto' and 'sync' read the OS setting via macOS defaults read -g AppleInterfaceStyle
or GNOME gsettings. They fall back to 'dark' when detection is unavailable.
-- Follow the OS appearance, switching in real time:
require('code-2026').setup { background = 'sync' }When background changes, vim.o.background is updated and the ColorScheme event
fires so statuslines and other plugins can pick the new palette.
Clears the background of Normal, NormalNC, the sign column, line numbers, fold column,
statusline, tabline, winbar and diagnostic virtual text, letting your terminal background
through.
require('code-2026').setup { transparent = true }Floating windows are not affected — see styles.floats.
| Value | Effect |
|---|---|
'solid' (default) |
Floats, popups and completion menus always painted (#202122) |
'transparent' |
Floats never painted |
'auto' |
Follows transparent |
Keeping this at 'solid' with transparent = true gives you a see-through editor with
readable popups, which is usually what you want:
require('code-2026').setup {
transparent = true,
styles = { floats = 'solid' },
}Affects NormalFloat, FloatBorder, FloatTitle, Pmenu*, and the float-based groups of
every plugin integration (telescope, blink.cmp, noice, lazy, mason, …).
Sets vim.g.terminal_color_0 … vim.g.terminal_color_15 so :terminal buffers use the
theme's ANSI palette. Set to false to leave them alone. See
Matching your terminal for the same values in Ghostty/kitty form.
Paints unfocused windows (NormalNC) with bg_inactive, a darkened bg. Ignored when
transparent = true.
Every entry is an nvim_set_hl() attribute
table merged into the highlight specs of that token family. Add an attribute with true,
remove one the theme sets with false:
require('code-2026').setup {
styles = {
comments = { italic = false }, -- drop the default italic
keywords = { italic = true },
functions = { bold = true },
types = { italic = true },
parameters = { italic = true },
headings = { bold = true, underline = true },
},
}Usable attributes: bold, italic, underline, undercurl, underdouble, underdotted,
underdashed, strikethrough, reverse, nocombine.
| Key | Default | Applies to (plus every group linked to these) |
|---|---|---|
comments |
{ italic = true } |
Comment, SpecialComment, @comment*, @markup.quote |
keywords |
— | Keyword, Statement, Label, StorageClass, PreProc, Include, Define, PreCondit, @keyword* |
conditionals |
— | Conditional, Repeat, Exception, @keyword.return, @keyword.conditional, @keyword.repeat |
functions |
— | Function, @function, @function.call, @function.builtin |
methods |
— | @function.method, @function.method.call, @lsp.type.method |
variables |
— | Identifier, @variable, @variable.builtin |
builtins |
{ italic = true } |
@variable.builtin, @function.builtin, @type.builtin, @module.builtin, @attribute.builtin |
parameters |
— | @variable.parameter, @lsp.type.parameter |
properties |
— | @property, @field, @variable.member, @lsp.type.property |
types |
— | Type, Structure, Typedef, @type*, @constructor, @lsp.type.class/enum/interface/struct |
strings |
— | String, Character, @string*, @string.escape, @string.regexp |
numbers |
— | Number, Float, @number, @number.float |
booleans |
— | Boolean, @boolean |
constants |
— | Constant, @constant*, @lsp.type.enumMember, @lsp.type.const, readonly variables |
operators |
— | Operator, @operator, @lsp.type.operator |
namespaces |
— | @module, @namespace, @lsp.type.namespace |
macros |
— | Macro, @function.macro, @constant.macro, @lsp.type.macro |
attributes |
— | @attribute, @tag.attribute, @lsp.type.decorator |
tags |
— | Tag, @tag, @tag.builtin |
headings |
{ bold = true } |
@markup.heading and @markup.heading.1 … .6 |
Static color overrides, applied on top of the base palette before anything is derived:
require('code-2026').setup {
palette = {
bg = '#0d0e0f',
keyword = '#f97583',
func = '#c9a0ff',
comment = '#7d8590',
},
}All palette keys
Backgrounds
| Key | Value | Used for |
|---|---|---|
bg |
#121314 |
editor background |
bg_alt |
#191a1b |
statusline, tabline, panels |
bg_menu |
#202122 |
floats, popups, completion |
bg_line |
#242526 |
cursorline, folds, inlay hints |
bg_widget |
#262728 |
LSP references, illuminate |
bg_select |
#276782 |
visual selection |
bg_match |
#1e4252 |
search matches |
border |
#2a2b2c |
window separators |
border_alt |
#333536 |
float borders |
Foregrounds
| Key | Value | Used for |
|---|---|---|
fg |
#bbbebf |
default text |
fg_alt |
#bfbfbf |
emphasized text |
fg_dim |
#8c8c8c |
secondary text |
fg_muted |
#555555 |
line numbers, whitespace |
white |
#ffffff |
text on accent backgrounds |
Accent
| Key | Value |
|---|---|
accent |
#3994bc |
accent_dim |
#297aa0 |
accent_alt |
#48a0c7 |
Syntax
| Key | Value | Key | Value |
|---|---|---|---|
comment |
#8b949e |
annotation |
#ffa657 |
string |
#a5d6ff |
param |
#ffa657 |
regex |
#7ee787 |
member |
#79c0ff |
number |
#79c0ff |
tag |
#7ee787 |
keyword |
#ff7b72 |
attr |
#79c0ff |
func |
#d2a8ff |
module |
#4ec9b0 |
type |
#4ec9b0 |
macro |
#48a0c7 |
variable |
#bbbebf |
operator |
#ff7b72 |
constant |
#79c0ff |
preproc |
#ff7b72 |
Diagnostics & diff
| Key | Value | Key | Value |
|---|---|---|---|
err |
#f44747 |
diff_add |
#1b3a1b |
warn |
#cd9731 |
diff_add_fg |
#7ee787 |
info |
#6796e6 |
diff_del |
#3a1b1b |
hint |
#3a94bc |
diff_del_fg |
#ffa198 |
ok |
#7ee787 |
diff_chg |
#2a2a4a |
debug |
#b267e6 |
diff_chg_fg |
#79c0ff |
diff_text |
#3a3a63 |
Derived — computed from the above after palette is applied, so they follow your
overrides. Set them explicitly in palette to opt out of the derivation.
| Key | Derivation |
|---|---|
bg_normal |
NONE when transparent, else bg |
bg_panel |
NONE when transparent, else bg_alt |
bg_float |
NONE when floats are transparent, else bg_menu |
bg_inactive |
bg darkened 35% (used by dim_inactive) |
err_bg warn_bg info_bg hint_bg |
diagnostic color blended 12% onto bg |
terminal |
table of the 16 ANSI colors |
Runs after palette, with the whole resolved palette — including the derived keys. Mutate
it in place:
local util = require 'code-2026.util'
require('code-2026').setup {
on_colors = function(colors)
colors.comment = util.lighten(colors.comment, 0.15) -- brighter comments
colors.bg_float = colors.bg -- floats share the editor bg
colors.terminal[1] = '#ff5555' -- custom ANSI red
end,
}util exposes blend(fg, bg, alpha), darken(hex, amount), lighten(hex, amount),
hex_to_rgb and rgb_to_hex.
Static highlight-group overrides, merged last — after the theme and after
on_highlights:
require('code-2026').setup {
highlights = {
Comment = { fg = '#6f7680' }, -- merged: the theme's italic is kept
CursorLine = { bg = '#1c1d1e' },
['@variable.parameter'] = { fg = '#ffa657', italic = true },
Visual = { link = 'CursorLine' }, -- a spec with `link` replaces the group outright
},
}Merge rules: table entries merge key-by-key, so you can change only fg and keep the rest.
Pass italic = false to clear an attribute. A spec containing link replaces the group
completely.
Same thing, programmatically, with the palette in hand. Receives the fully built highlight
table before highlights is merged on top:
require('code-2026').setup {
on_highlights = function(hl, colors)
hl.LineNr = { fg = colors.fg_dim }
hl.WinSeparator = { fg = colors.accent_dim }
hl.CursorLineNr = { fg = colors.accent, bold = true }
hl.MyPluginTitle = { fg = colors.accent, bold = true } -- new groups are fine too
end,
}All plugin integrations are on by default. Disable any of them by key — the theme then leaves those groups untouched, so the plugin's own defaults (or your config) apply:
require('code-2026').setup {
plugins = { bufferline = false, dap = false },
}| Key | Plugin | Key | Plugin |
|---|---|---|---|
gitsigns |
gitsigns.nvim | neo_tree |
neo-tree.nvim |
telescope |
telescope.nvim | nvim_tree |
nvim-tree.lua |
fzf_lua |
fzf-lua | oil |
oil.nvim |
snacks |
snacks.nvim (picker, notifier, dashboard, indent) | indent_blankline |
indent-blankline.nvim |
blink |
blink.cmp | mini |
mini.nvim (statusline, tabline, pick, files, diff, indentscope) |
cmp |
nvim-cmp | lazy |
lazy.nvim |
bufferline |
bufferline.nvim | mason |
mason.nvim |
which_key |
which-key.nvim | trouble |
trouble.nvim |
notify |
nvim-notify | flash |
flash.nvim |
noice |
noice.nvim | illuminate |
vim-illuminate |
dashboard |
dashboard-nvim, alpha-nvim | treesitter_context |
nvim-treesitter-context |
yanky |
yanky.nvim | rainbow |
rainbow-delimiters.nvim |
fidget |
fidget.nvim | dap |
nvim-dap, nvim-dap-ui |
markdown |
render-markdown.nvim | navic |
nvim-navic |
require('lualine').setup { options = { theme = 'code-2026' } }The theme reads the resolved palette, so it follows your palette / on_colors overrides.
local dark = require 'code-2026'
dark.setup(opts) -- store config; reapplies the theme if it is already active
dark.load(opts) -- apply the colorscheme; opts override the stored config for this call
dark.colors(opts) -- resolved palette table
dark.highlights(opts) -- resolved highlight table + palette, without applying themdark.colors() is handy for statuslines and other plugins that need to match:
local c = require('code-2026').colors()
vim.api.nvim_set_hl(0, 'MyGroup', { fg = c.accent, bg = c.bg_alt })The ANSI palette used for :terminal and exported via vim.g.terminal_color_*:
| Normal | Bright | ||
|---|---|---|---|
| black | #202122 |
bright black | #555555 |
| red | #ff7b72 |
bright red | #ffa198 |
| green | #7ee787 |
bright green | #91eb99 |
| yellow | #cd9731 |
bright yellow | #ffa657 |
| blue | #79c0ff |
bright blue | #a5d6ff |
| magenta | #d2a8ff |
bright magenta | #b267e6 |
| cyan | #4ec9b0 |
bright cyan | #71d4c0 |
| white | #bbbebf |
bright white | #ffffff |
With background #121314, foreground #bbbebf, cursor #bbbebf, selection #276782 on
#ffffff. Ready-made theme files:
Ghostty ·
kitty.
Ghostty
background = #121314
foreground = #bbbebf
cursor-color = #bbbebf
cursor-text = #121314
selection-background = #276782
selection-foreground = #ffffff
palette = 0=#202122
palette = 1=#ff7b72
palette = 2=#7ee787
palette = 3=#cd9731
palette = 4=#79c0ff
palette = 5=#d2a8ff
palette = 6=#4ec9b0
palette = 7=#bbbebf
palette = 8=#555555
palette = 9=#ffa198
palette = 10=#91eb99
palette = 11=#ffa657
palette = 12=#a5d6ff
palette = 13=#b267e6
palette = 14=#71d4c0
palette = 15=#ffffffkitty
background #121314
foreground #bbbebf
cursor #bbbebf
cursor_text_color #121314
selection_background #276782
selection_foreground #ffffff
url_color #48a0c7
color0 #202122
color8 #555555
color1 #ff7b72
color9 #ffa198
color2 #7ee787
color10 #91eb99
color3 #cd9731
color11 #ffa657
color4 #79c0ff
color12 #a5d6ff
color5 #d2a8ff
color13 #b267e6
color6 #4ec9b0
color14 #71d4c0
color7 #bbbebf
color15 #ffffffWith background #FFFFFF, foreground #202020, cursor #0069CC, selection #C2DAF5 on
#202020.
Ghostty
background = #FFFFFF
foreground = #202020
cursor-color = #0069CC
cursor-text = #FFFFFF
selection-background = #C2DAF5
selection-foreground = #202020
palette = 0=#f0f1f2
palette = 1=#cf222e
palette = 2=#116329
palette = 3=#953800
palette = 4=#0550ae
palette = 5=#8250df
palette = 6=#116329
palette = 7=#202020
palette = 8=#999999
palette = 9=#cf222e
palette = 10=#116329
palette = 11=#953800
palette = 12=#0550ae
palette = 13=#8250df
palette = 14=#116329
palette = 15=#ffffffkitty
background #FFFFFF
foreground #202020
cursor #0069CC
cursor_text_color #FFFFFF
selection_background #C2DAF5
selection_foreground #202020
url_color #0069CC
color0 #f0f1f2
color8 #999999
color1 #cf222e
color9 #cf222e
color2 #116329
color10 #116329
color3 #953800
color11 #953800
color4 #0550ae
color12 #0550ae
color5 #8250df
color13 #8250df
color6 #116329
color14 #116329
color7 #202020
color15 #ffffffTransparent editor, solid popups, italic keywords
require('code-2026').setup {
transparent = true,
styles = {
floats = 'solid',
keywords = { italic = true },
comments = { italic = true },
},
}No italics anywhere
require('code-2026').setup {
styles = {
comments = { italic = false },
builtins = { italic = false },
},
}Darker background, flatter UI
require('code-2026').setup {
palette = { bg = '#0b0c0d', bg_alt = '#0b0c0d', bg_menu = '#141516' },
on_highlights = function(hl, c)
hl.WinSeparator = { fg = c.bg_line }
hl.SignColumn = { bg = c.bg_normal }
end,
}Reuse the palette in your own config
local c = require('code-2026').colors()
require('lualine').setup {
options = { theme = 'code-2026' },
sections = { lualine_c = { { 'filename', color = { fg = c.accent_alt } } } },
}colors/code-2026.lua :colorscheme entry point
lua/code-2026/init.lua setup / load / colors / highlights
lua/code-2026/config.lua defaults and validation
lua/code-2026/palette.lua base palette and derived colors
lua/code-2026/theme.lua group assembly and override pipeline
lua/code-2026/util.lua color math and merge helpers
lua/code-2026/groups/ editor, syntax, treesitter, lsp, plugins
lua/lualine/themes/code-2026.lua lualine theme
doc/code-2026.txt :help code-2026
Override precedence, in order: base palette → palette → derived colors → on_colors →
highlight groups → on_highlights → highlights.
- D0nw0r/dark2026.nvim by D0nw0r — the original port this is built on. Licensed MIT; its copyright notice is retained in LICENSE.
- Microsoft's VS Code Dark Modern 2026 theme — the source palette.
