Advanced Usage

February 16, 2026 · View on GitHub

Welcome to the advanced usage guide for nvim-highlite! This will teach you how to create your own colorscheme using this plugin, whether it be from scratch, one of our built-in colorschemes, or another existing colorscheme.

tl;dr

Creating Your Colorscheme

At last, you are ready to create a colorscheme! You should follow these sections in order to ensure your success.

Where to Put Your Colorscheme

Colorschemes must be defined in a very specific location in order to be visible to Neovim's :colorscheme command. In order for :colorscheme foo to work, there must be a colors/foo.vim or colors/foo.lua file located within your &runtimepath.

Note

To check your runtime path, do :lua for _, p in ipairs(vim.opt.rtp:get()) do print(p) end.

Personal Colorscheme

If you are making this colorscheme for yourself, that file should be in your configuration directory. Run this snippet to show where that is:

:lua = vim.fn.stdpath('config') .. '/colors/'

Re-distributable Colorscheme

If you are making this colorscheme for others to be able to install— first, consider contributing! I'd love to have more colorschemes built-in with this repository.

If you'd rather have the colorscheme live separately from this repository, create a repository and git clone https://github.com/YOUR_USERNAME/YOUR_REPO YOUR_PROGRAMMING_DIR/YOUR_REPO. You can then use a plugin manager to load this directory— here is an example for lazy.nvim:

require('lazy').setup(
  {
    {'YOUR_USERNAME/YOUR_REPO',
      config = function() vim.api.nvim_command 'colorscheme YOUR_COLORSCHEME' end,
      dependencies = 'Iron-E/nvim-highlite',
      priority = 1000,
    },
  },
  {dev = {fallback = true, path = 'YOUR_PROGRAMMING_DIR', patterns = {'YOUR_USERNAME'}}}
)

Make sure to replace YOUR_COLORSCHEME, YOUR_PROGRAMMING_DIR, YOUR_REPO, YOUR_USERNAME with what those values would actually be.

1. Generating a Palette

Now that your file has been created, you can start developing your colorscheme. The first thing to do is decide on your palette: will base it off of one that is built-in, or create a new one?

  • If you like the look of one of the built-in colorschemes, you should use one of the built-in palettes.
  • If you want to make a new colorscheme, or port another colorscheme over to highlite, you should derive it.

Deriving a Palette

There are currently 80+ fields to a palette, and that number will probably only grow over time. To make the process of creating a palette for nvim-highlite easier, there is Palette.derive. Using this function, only 6(!!) colors need to be defined:

local Palette = require 'highlite.color.palette' --- @type highlite.color.Palette
local colors = Palette.derive('dark', {
  bg = '#202020', -- colors can be a string
  error = 0xAA0000, -- or an integer
  ok = '#00aa00',
  text = '#cccccc',
  statement = '#33ccFF',
  storage = '#cc7700',
})

Of course… that means your colorscheme only uses six colors. Even though only those six are required, it is recommended to define more. With more information, derive becomes more accurate. Some groups that are recommended are:

  • func
  • hint (& info, if you want them be distinct from hints)
  • uri
  • warning

Here is another example showing all of the above, which includes detection of light/dark backgrounds:

local Palette = require 'highlite.color.palette' --- @type highlite.color.Palette

local bg = vim.api.nvim_get_option 'background'
local colors = Palette.derive(bg, bg == 'dark' and {
  bg = '#202020',
  error = '#aa0000',
  func = '#cc00aa',
  hint = '#cc0055',
  info = '#ccbb88',
  ok = '#33bb55',
  statement = '#33ccFF',
  storage = '#cc7700',
  text = '#cccccc',
  uri = '#00aa00',
  warning = '#ffbb00',
} or {
  -- light palette goes here
})
1b. Creating a Terminal Palette (Optional)

To define a terminal palette, all you have to do is define a list with colors in the following order:

  1. black
  2. dark red
  3. dark green
  4. dark yellow / orange
  5. blue
  6. dark magenta
  7. dark cyan
  8. gray / "dark white"
  9. dark gray / "light black"
  10. red
  11. green
  12. yellow
  13. light blue
  14. magenta
  15. cyan
  16. white

Warning

While other colors may either be a string (e.g. '#FFFFFF') or an integer (e.g. 0xFFFFFF), but the colors in the terminal palette must be strings.

For example:

local terminal_palette = {
  '#101017', '#aa0000', '#33bb55', '#cc7700', '#133099', '#990066', '#33ccFF', '#cccccc',
  '#808080', '#FF2222', '#00aa00', '#7f6f20', '#1259ff', '#cc00aa', '#3388aa', '#ffffff',
}

Using a Built-in Palette

local Highlite = require 'highlite' --- @type Highlite

local palette, terminal_palette = Highlite.palette 'highlite' -- or any of the built-in palettes, e.g. 'ayu'

The terminal_palette will automatically be nil when you turn off terminal highlighting in generate.

List of Built-in Palettes
NameDescription
ayuBased on ayu from ayu-theme/ayu-vim
everforestBased on everforest from sainnhe/everforest
gruvbox-materialBased on gruvbox-material from sainnhe/gruvbox-material
gruvboxBased on gruvbox from morhetz/gruvbox
highliteThe original nvim-highlite palette
icebergBased on iceberg from cocopon/iceberg.vim
molokaiBased on molokai from tomasr/molokai
papercolorBased on papercolor from nlknguyen/papercolor-theme
seoul256-lightBased on seoul256-light from junegunn/seoul256.vim
seoul256Based on seoul256 from junegunn/seoul256.vim
solarized8-flatBased on solarized8-flat from lifepillar/vim-solarized8
solarized8-highBased on solarized8-high from lifepillar/vim-solarized8
solarized8-lowBased on solarized8-low from lifepillar/vim-solarized8
solarized8Based on solarized8 from lifepillar/vim-solarized8
sonokaiBased on sonokai from sainnhe/sonokai

List of Palette Colors

Remember: you do not need to define all of these.

FieldDefaultDefinition
annotationattribute
attribute_builtinspecialbuiltin attributes
attributepreproc
bg_contrast_highbgan accent to the bg color which contrasts
bg_contrast_lowbgan accent to the bg color which barely contrasts
bgthe background color
booleanconstanttrue and false
buffer_activetext_contrast_bg_highthe underline of an active buffer
buffer_alternatesearchthe underline of the alternate buffer
buffer_currentselectthe underline of the current buffer
character_specialspecialspecial characters e.g. wildcards
characterstringcharacter literals (e.g. 'a')
classstructuredata with behavior
comment_documentationcommentdocumentation comments
comment_specialspecial tokens in a comment
commenttext_contrast_bg_lownormal comments
conditionalkeyworde.g. if, match, switch
constant_builtinspecialbuilt-in constant values
constantstorageconstant values
constructortypeinvoked by new
decoratorattribute
definepreproce.g. #define in C
diff_addokadded text
diff_changenumberchanged text, also applies to modified buffers in the tabline
diff_deleteerrorremoved text
diff_syntax_addokadded text within a (universal) diff
diff_syntax_changenumberchanged text within a (universal) diff
diff_syntax_deleteerrorremoved text within a (universal) diff
enumstructurean enumeration
errordiagnostic errors
eventloopevents e.g. public event FooEventHandler FooEvent in C#
field_enumconstanta variant of an enum e.g. Ok in Rust
fieldpropertya member of a class/struct which is accessed directly
floatnumberfloating point numbers
foldfuncthe background of &foldtext
func_builtinspecialbuiltin function
funcstatementfunctions, e.g. print('a')
heading_1errorlevel 1 heading
heading_2warninglevel 2 heading
heading_3diff_changelevel 3 heading
heading_4stringlevel 4 heading
heading_5typelevel 5 heading
heading_6foldlevel 6 heading
hintinfodiagnostic hints
identifiertextmisc. identifier
includepreproce.g. #include in C
infowarningdiagnostic info
interface_builtintype_builtine.g. interface Foo
interfacetypee.g. interface Foo
keyword_coroutinekeyworde.g. await
keyword_functiontype_builtine.g. fn in Rust, def in Python
keyword_modifierkeywordmodifiers for items, e.g. public
keyword_operatoroperatore.g. and in Lua
keyword_returnkeyworde.g. return, yield
keyword_typekeyworde.g. "struct"
keywordstatementkeywords, e.g. local in Lua
labelspecialswitch cases, loop labels (e.g. 'outer for x in y { break 'outer } in Rust)
loopconditionale.g. loop, for, while
macrodefinee.g. println! in Rust
markup_link_labelstring_specialmarkup link/reference descriptions
markup_list_checkedstring_specialmarkup todo-style markers (checked)
markup_list_uncheckedstring_specialmarkup todo-style markers (unchecked)
markup_liststring_specialmarkup list markers
markup_quotecomment
messageinfoBuiltin Vim messages
methodfuncfunction of a class
namespace_builtinnamespacenamespace, but builtin
namespaceurie.g. foo::bar in Rust/C++
numberconstantnumber literals (e.g 9)
okpositive reinforcement from LSP or Git
operatorkeyworde.g. +, -, &&
parameter_builtinspeciala builtin function parameter
parameteridentifiera function parameter
preproc_conditionalpreprocpre-processor conditionals (e.g. #[cfg] in Rust)
preprocfuncpre-processor commands (e.g. #[derive] in Rust)
propertyidentifiera member of a class or structure which is transparently a function
punctuation_bracketpunctuationbalanced punctuation, e.g. [], "", ()
punctuation_delimiterpunctuationsingle punctuation, e.g. ,, ;
punctuation_specialstring_specialspecial punctuation, e.g. {} in a format string
punctuationtext_contrast_bg_highmisc. delimiters
searchselectDoing :/ or :s/
selectbg_contrast_lowselected text, e.g. in visual mode or selecting LSP completions
specialstatementunclassified special symbols in a language
statementprogramming language statements which do not fall into another category
storagepointer symbols (e.g. &foo)
string_documentationcomment_documentationstrings documenting code
string_escapecharacterescaped characters in a string
string_regexstringregular expressions
string_special_symbolstring_specialsymbols or atoms
string_specialidentifierspecial strings (e.g. dates)
stringconstanta misc. string
structuretypeplain-old data
syntax_errorerrornon-diagnostic errors; sometimes shown in the actual highlighting of the code you write
tag_attributefieldXML/HTML tag attributes
tag_delimiterpunctuation_bracket</> in XML/HTML tags
tagstructureXML/HTML tags
text_contrast_bg_hightextan accent to the text color which contrasts
text_contrast_bg_lowtextan accent to the text color which barely contrasts
text_environment_namelabeltext environment name
text_environmenttext_contrast_bg_lowtext environment delimiter
text_literalstatementliteral text
text_mathnumbermathematical text
text_referenceurireferences to variables in text
textplaintext
throwerrora keyword which triggers or receives errors (e.g. throw MyError)
todomessageTODO comments
type_builtintypea type from the standard library
type_definitiontypetypedefs
type_parametertypegeneric type
typekeywordtype names e.g. int, String
uritextlinks (HTTP, tags, match paren, etc).
variable_builtinkeyworda variable from the language, e.g. self
variableidentifiera variable
warningerrordiagnostic warnings
Extending the Palette

You may add extra colors to the palette, if what is available is not fine-grained enough. You can add as many colors as you like. Example:

local Highlite = require 'highlite' --- @type Highlite

local palette, terminal_palette = Highlite.palette 'highlite'

-- also works with derive
palette.my_custom_color = '#FF0000'

local groups = Highlite.groups('default', palette)
groups.Error = {fg = palette.my_custom_color}

-- … other colorscheme logic

highlite.Color Reference

Note

This section is optional. Both Palette.derive and the built-in palettes handle colors automatically. However, if you wish to have finer control, you may use these functions.

Color.saturate()
function Color.saturate(color: integer|string, factor: integer) -> integer

Saturates the given color multiplicatively using factor. Example:

local Color = require 'highlite.color' --- @type highlite.Color

assert(Color.saturate('#202020', 2) == 0x404040)
assert(Color.saturate(0x202020, 0.5) == 0x101010)

2. Generating Groups

To generate groups, you can do:

local Highlite = require 'highlite' --- @type Highlite

local palette = … -- derive or get a built-in palette
local groups = Highlite.groups('default', palette) -- or any of the built-in groups

List of Built-in Groups

NameDescriptionParent Group
defaultThe default groups: all Neovim built-ins and some Lua plugins.
highliteOverrides for the highlite pallete.default
seoul256-lightOverrides for the seoul256-light pallete.seoul256
seoul256Overrides for the seoul256 pallete.default

2a. Overriding Groups (Optional)

nvim-highlite groups follow the format of nvim_set_hl's {val} parameter. Example:

local colors = require('highlite.color.palette').derive(…) -- see "Deriving a Palette" for more info
local normal = {fg = colors.text, bg = colors.bg}

nvim-highlite also accepts a shorthand :highlight link syntax:

local float = 'Number' -- same as `{link = 'Number'}`

nvim-highlite provides a convenient way to resolve highlight links within the defined colorscheme:

--- @param group string
--- @return nil|table

Example:

local palette = … -- see instructions for generating a palette
local groups = … -- see above instructions for generating groups

-- create a new highlight group `Foo`
groups.Foo = {fg = palette.text}

-- link `Bar` to `Foo`
groups.Bar = 'Foo'

-- `groups.XYZ` will retrieve `XYZ`'s raw value.
assert(vim.deep_equal(groups.Foo, {fg = palette.text}))
assert(vim.deep_equal(groups.Bar, 'Foo'))

-- `groups'XYZ'` will retrieve `XYZ`'s unlinked value
assert(vim.deep_equal(groups'Foo', {fg = palette.text}))
assert(vim.deep_equal(groups'Bar', {fg = palette.text}))

--[[ You can use this to choose whether to reference a link or a definition. ]]

-- link 'Baz' to 'Foo', since 'Bar' is linked to 'Foo' too
groups.Baz = groups.Bar

-- create a new highlight group `Xyz` using `Bar`'s unlinked `fg` attribute
groups.Xyz = {fg = groups'Bar'.fg}

-- you can chain resolves, in case one group isn't available
groups.Zyx = {fg = (groups"Doesn't Exist" or groups'Might Exist' or {}).fg}
Limitations
  • For LSP and treesitter groups: if @foo.bar is not defined explicitly, the un-linker will not attempt to lookup @foo.
    • As a workaround, you can do groups'@foo.bar' or groups'@foo' or {} to check for the existence of fallback highlights manually.
highlite.Groups Reference

Note

This section is optional. Feel free to use vim.deepcopy, vim.tbl_extend, etc. and come back later if you feel like you need more performance.

Working with raw highlight groups can be slow using built-in methods.

For example, let's say we want to highlight Lua's [[/]] delimiters differently than normal delimiters. How would you do that normally?

local delimiter = {fg = '#FFFFFF'}
local lua_delimiter = vim.tbl_extend('keep', {italic = true}, delimiter)

Using the Bench function in my config, you can determine that this is ⪆4× faster:

local delimiter = {fg = '#FFFFFF'}
local lua_delimiter = Groups.extend({italic = true}, delimiter)

All of the functions in Groups are specialized for working with highlight groups; they can be minimal in ways which built-in functions should not be. You should prefer to use these functions, where they apply.

Warning

None of the following functions work with the shorthand link syntax:

local Groups = require 'highlite.groups' --- @type highlight.Groups
local DiagnosticError = 'Error'
local cloned = Groups.clone(DiagnosticError) -- invalid!

There is a way to resolve highlight links into a valid group definition— see Resolving Highlight Links.

Groups.clone()
function Groups.clone(group: table<string, any>) -> table<string, any>

Return a new highlight group definition with the same attributes of some group.

Perf

Faster than vim.deepcopy for the case of copying highlight groups.

Example:

local Groups = require 'highlite.groups' --- @type highlite.Groups

local delimiter = {fg = '#FFFFFF'}
local err = {fg = '#c0c0c0', bg = '#aa0000'}
local err_delimiter = Groups.extend(Groups.clone(delimiter), err)

assert(not vim.deep_equal(delimiter, delimiter_clone))
Groups.extend()
function Groups.extend(child: table<string, any>, parent: table<string, any>)
  -> table<string, any>

Assign all attributes of parent to child which are not already defined.

Perf

Faster than vim.tbl_extend for the case of extending highlight groups.

Warning

This mutates child! Use Groups.clone() first if this is a problem.

Example:

local Groups = require 'highlite.groups' --- @type highlite.Groups

local char = {fg = '#FF0000'}
local special_char = Groups.extend({italic = true}, char)
Groups.extend_selected()
function Groups.extend_selected(
  child: table<string, any>,
  parent: table<string, any>,
  opts: table<string, boolean>
) -> table<string, any>

Like Groups.extend, but allows forcing and skipping attributes. Example:

local Groups = require 'highlite.groups' --- @type highlite.Groups

local white_bold = {fg = '#FFFFFF', bold = true}
local green_red_italic = {fg = '#00FF00', bg = '#FF0000', bold = false, italic = true}
local white_red_italic = Groups.extend_selected(green_red_italic, white_bold_text, {fg = true})
local green_red_bold_italic = Groups.extend_selected(green_red_italic, white_bold_text, {bold = true, fg = false})

Perf

Faster than vim.tbl_extending the output of vim.tbl_filter for the case of extending highlight groups.

Warning

This mutates child! Use Groups.clone() first if this is a problem.

3. Generate

Finally, now that you have your palette and your groups, you can generate your colorscheme:

require('highlite').generate(
  '<name>', -- replace `<name>` with the name of the file you created in step 1
  groups,
  terminal_palette -- OPTIONAL: set the terminal colors
)

Complete Example

local Groups = require 'highlite.groups' --- @type highlite.Groups
local Highlite = require 'highlite' --- @type Highlite
local Palette = require 'highlite.color.palette' --- @type highlite.color.Palette

--[[ Generate Palette
  NOTE: this section shows using `derive`, but you can also use one of the builtin palettes.
        See "Generating a Palette" for more information.
]]

local bg = vim.api.nvim_get_option 'background' -- get the background
local palette = Palette.derive(bg, bg == 'dark' and {
  bg = '#202020',
  error = '#aa0000',
  func = '#cc00aa',
  hint = '#cc0055',
  info = '#ccbb88',
  ok = '#33bb55',
  statement = '#33ccFF',
  storage = '#cc7700',
  text = '#cccccc',
  uri = '#00aa00',
  warning = '#ffbb00',
} or {
  -- light palette goes here
})

--- NOTE: this part is optional!
--- @type highlite.color.palette.terminal
local terminal_palette = {
  '#101017', '#aa0000', '#33bb55', '#cc7700', '#133099', '#990066', '#33ccFF', '#cccccc',
  '#808080', '#FF2222', '#00aa00', '#7f6f20', '#1259ff', '#cc00aa', '#3388aa', '#ffffff',
}

--[[ Generate Groups ]]
local groups = Highlite.groups('default', palette)

--[[ Override Groups ]]

-- Get the bracket group, and extend it to add italics
groups.luaStringLongTag = Groups.extend({italic = true}, groups '@punctuation.bracket')

-- Use `msgsep` in `fillchars`? Override `MsgSeparator` to only highlight the foreground
groups.MsgSeparator = {fg = colors.text_contrast_bg_high}

--[[ Generate ]]
Highlite.generate('highlite-example', groups, terminal_palette)

Importing Colorschemes

Note

If you are trying to port a colorscheme as a contribution to nvim-highlite, there is an even easier way. See Porting Colorschemes.

Note

This feature requires requires Neovim 0.9+.

nvim-highlite provides an import module to allow importing colorschemes from various formats to one that it recognizes.

Data Type&bgField
Color PaletteDarkdark.palette
Color PaletteLightlight.palette
Terminal PaletteDarkdark.terminal
Terminal PaletteLightlight.terminal

For example, if you do:

local Import = require 'highlite.import' --- @type highlite.Import
local imported = Import.nvim 'zellner'

Then imported.dark.palette is the color palette for a dark &background.

Warning

Importing is slow! You should not import another colorscheme in your colorscheme file. For example, this colorscheme will load slowly:

local Import = require 'highlite.import' --- @type highlite.Import
local Highlite = require 'highlite' --- @type Highlite

local bg = vim.api.nvim_get_option 'background'
local imported = Import.nvim 'zellner'

Highlite.generate(
  'example',
  Highlite.groups('default', imported[bg].palette),
  imported[bg].terminal
)

Instead, you should use something like MiniMisc.put_text or vim.inspect & nvim_buf_set_text to insert the import into the colorscheme file directly.

Vim colorschemes

Supports any colorscheme that can be accessed via :colorscheme. Doesn't matter if it was written in Lua, Python, Rust, VimScript, etc.

local Import = require 'highlite.import' --- @type highlite.Import
local imported = Import.nvim 'zellner'

Tips

Helpful Tools

:Inspect / :InspectTree

Neovim 0.9+ comes with the built-in :Inspect & :InspectTree commands. They let you analyze the highlights that are being applied to the character under your cursor.

mini.colors

echasnovski/mini.colors is a fantastic plugin (as is the rest of mini.nvim) that allows you to easily manipulate colors, fiddle with existing colorschemes, and generate light/dark variants of a specific palette.

If you have used rktjmp/lush.nvim and miss its suite of utilities to work with colors / preview your work in this plugin, mini.colors may be able to help bridge the gap!