All posts

How to Create a Custom Neovim Colorscheme

You create a custom Neovim colorscheme by defining highlight groups — colors for syntax tokens, UI elements, and diagnostics — in a Lua file and loading it with colorscheme. The manual route gives full control with the modern nvim_set_hl API; a visual tool like Nvim Colors exports a flat .lua file you can drop into your config in minutes.

What Is a Neovim Colorscheme?

A Neovim colorscheme is a Lua file (or legacy Vim script) that sets highlight groups. Each group maps a UI or syntax element — comments, strings, line numbers, error messages — to foreground color, background color, and optional styles like bold or italic.

Neovim uses Tree-sitter and legacy syntax highlighting to assign tokens to groups like @string, @keyword, and @function. Your colorscheme tells Neovim what each group should look like on screen.

Without a colorscheme, Neovim falls back to defaults that may not match your terminal or taste. A custom theme lets you optimize for readability, brand colors, or a specific mood.

How Do I Create a Colorscheme Manually in Lua?

Manual creation follows four steps: enable true colors, create a Lua file, define highlight groups, and load the theme.

Step 1 — Enable true colors

Neovim needs 24-bit color support for hex values to render correctly:

vim.opt.termguicolors = true

Add this to your init.lua or options.lua before loading any colorscheme.

Step 2 — Create a colorscheme file

Place your theme at ~/.config/nvim/lua/colorschemes/mytheme.lua:

local M = {}

function M.setup()
  vim.cmd('hi clear')
  if vim.fn.exists('syntax_on') then
    vim.cmd('syntax reset')
  end
  vim.o.background = 'dark'
  vim.g.colors_name = 'mytheme'

  local colors = {
    bg = '#1a1b26',
    fg = '#c0caf5',
    comment = '#565f89',
    string = '#9ece6a',
    keyword = '#bb9af7',
    function_ = '#7aa2f7',
    type = '#2ac3de',
    number = '#ff9e64',
  }

  vim.api.nvim_set_hl(0, 'Normal', { fg = colors.fg, bg = colors.bg })
  vim.api.nvim_set_hl(0, 'Comment', { fg = colors.comment, italic = true })
  vim.api.nvim_set_hl(0, 'String', { fg = colors.string })
  vim.api.nvim_set_hl(0, 'Number', { fg = colors.number })
  vim.api.nvim_set_hl(0, 'Keyword', { fg = colors.keyword })
  vim.api.nvim_set_hl(0, 'Function', { fg = colors.function_ })
  vim.api.nvim_set_hl(0, 'Type', { fg = colors.type })
  vim.api.nvim_set_hl(0, 'LineNr', { fg = colors.comment })
  vim.api.nvim_set_hl(0, 'CursorLine', { bg = '#24283b' })
  vim.api.nvim_set_hl(0, 'Visual', { bg = '#33467c' })
end

return M

Step 3 — Load the colorscheme

In your config:

require('colorschemes.mytheme').setup()

This module pattern is common for themes you publish as plugins or maintain long-term. You can also register a standard :colorscheme command with a colors/mytheme.vim shim that calls setup().

Step 4 — Extend for Tree-sitter and plugins

Real-world themes define dozens of groups. Important Tree-sitter groups include @keyword, @string, @function, @variable, and @comment. Plugin groups like TelescopeBorder, NvimTreeNormal, and DiagnosticError keep your UI consistent.

Use :Inspect on any element under the cursor to see its highlight group, then add a matching nvim_set_hl call. This iterative process is powerful but time-consuming — which is why many developers use a generator instead.

How Does Nvim Colors Speed Up Colorscheme Creation?

Nvim Colors is a free web tool at nvimcolors.com built specifically for Neovim colorscheme creation. Instead of hand-writing highlight definitions, you:

  1. Pick base background and foreground colors
  2. Tune syntax groups with live preview
  3. Export a complete .lua file
  4. Drop the file into your Neovim config

The generator produces a flat script — not a module — that sets highlights with vim.cmd('highlight ...') when Neovim loads the file. A typical export looks like this:

-- EVERGREEN
-- created on https://nvimcolors.com

vim.cmd('highlight clear')
vim.cmd('syntax reset')

-- Basic UI elements
vim.cmd('highlight Normal guibg=#304b50 guifg=#d6dbdc')
vim.cmd('highlight CursorLine guibg=#415a5e')
vim.cmd('highlight LineNr guifg=#839396')
vim.cmd('highlight Visual guibg=#62777a')

-- Syntax highlighting
vim.cmd('highlight Comment guifg=#849497 gui=italic')
vim.cmd('highlight String guifg=#fc9f9b')
vim.cmd('highlight Keyword guifg=#a2dffd gui=italic')
vim.cmd('highlight Function guifg=#2bdcab')

-- Messages
vim.cmd('highlight DiagnosticError guifg=#ff5c5c')
vim.cmd('highlight DiagnosticWarn guifg=#e2ce3c')

The export also covers popup menus, search highlights, diagnostics, and a handful of plugin groups (Telescope, Copilot). It includes a few Tree-sitter groups for HTML tags (@tag.delimiter, @tag.attribute) alongside classic syntax groups like String and Keyword, which Tree-sitter often links to automatically.

Install an Nvim Colors export

  1. Download your .lua file from the generator
  2. Save it as ~/.config/nvim/colors/mytheme.lua (use your chosen theme name)
  3. Add these lines to the end of your init.lua:
vim.opt.termguicolors = true
vim.cmd('colorscheme mytheme')
  1. Restart Neovim

You can also browse ready-made presets like nicovim, exord, and evergreen, customize them in the generator, and export your own variant.

For most developers who care about colors but not about maintaining a colorscheme codebase, this workflow is the fastest path from idea to working theme. If you later want a publishable plugin-style theme, refactor the export into a module with nvim_set_hl — the manual section above shows that pattern.

What Highlight Groups Matter Most?

Focus on these groups first — they cover most of what you read during a coding session:

Group Purpose
Normal Default text and editor background
Comment Comments — should be muted, not invisible
String String literals
Number Numeric literals
Keyword Language keywords (if, return, const)
Function Function names and calls
Type Types, classes, interfaces
LineNr Line numbers in the gutter
CursorLine Highlight for the current line
Visual Selected text
DiagnosticError Error underlines and signs

Once these look right, expand to diff highlights, search matches, and plugin-specific groups.

How Do I Test My Colorscheme?

Test across multiple file types and conditions:

  • Open .lua, .ts, .py, and .md files to check syntax coverage
  • Trigger :messages and :checkhealth to see diagnostic colors
  • Toggle :set number and split windows to verify UI groups
  • View code in bright and dim lighting — contrast matters for long sessions (see our color theory guide)

For Nvim Colors exports, reload with :colorscheme mytheme after editing the file. For module-based themes, run :source ~/.config/nvim/lua/colorschemes/mytheme.lua and call setup() again, or restart Neovim.

How Do I Share or Publish My Theme?

Package your .lua file in a Git repo with a README and install instructions. Popular patterns:

  • Local only — keep the file in your dotfiles repo
  • Plugin manager — publish as a Neovim plugin on GitHub and install with lazy.nvim or packer
  • Nvim Colors presets — start from community presets and share your exported file with teammates

If you want others to install your theme, document the install path (colors/ for flat exports, lua/colorschemes/ for modules) and the termguicolors requirement.

Frequently Asked Questions

What is the easiest way to create a Neovim colorscheme?

The fastest path is a visual generator like Nvim Colors. Pick base colors, tune syntax groups, and download a complete .lua file. Manual creation in Lua gives more control but takes longer.

Do I need to know Lua to make a Neovim colorscheme?

For a from-scratch colorscheme, yes — you define highlight groups with vim.api.nvim_set_hl in a Lua file. Nvim Colors generates a ready-to-use .lua file with vim.cmd('highlight ...') calls so you can skip writing them by hand.

Where do I put a custom Neovim colorscheme file?

For Nvim Colors exports, save the file as ~/.config/nvim/colors/mytheme.lua and load it with vim.cmd('colorscheme mytheme'). For a hand-written module, use ~/.config/nvim/lua/colorschemes/mytheme.lua and require('colorschemes.mytheme').setup().

What highlight groups should a Neovim colorscheme define?

At minimum, define Normal, Comment, String, Number, Keyword, Function, Type, and Identifier. UI groups like LineNr, CursorLine, and Visual matter for daily comfort.

Can I base my colorscheme on an existing theme?

Yes. Fork an existing colorscheme repo or use Nvim Colors presets to start from a palette you like, then customize and export your own variant.