micdzu/aalto.nvim
A semantic colorscheme with a perceptual OKLCH engine, featuring four semantic roles and both dark and light variants.
Aalto.nvim
Color that means something. Everything else fades away.

Most colorschemes are hex tables with opinions. Aalto is a perceptual color engine that happens to produce a colorscheme.
The difference in practice: when you change a color in Aalto, you change a hue identity. Contrast, saturation, and the visual hierarchy between roles are recomputed automatically in OKLCH space — a perceptually uniform color model where equal numeric steps look equal to the eye. You get a coherent result without manually tweaking six related values.
The idea
Aalto maps all of code down to four semantic roles and colors those. Everything else — keywords, operators, punctuation, variables — renders in neutral foreground.
| Role | Meaning | Examples |
|---|---|---|
definition |
Structure | functions, types, classes, modules |
constant |
Values | numbers, booleans, enum members |
string |
Data | string literals |
comment |
Context | comments, documentation |
The hierarchy definition > constant > string > comment is enforced
perceptually, not just numerically — each role is placed at a deliberate
distance from the background in OKLCH lightness, so the prominence ordering
holds across both dark and light variants regardless of hue.
The result is an editor that quietly shows you the shape of your code. The best colorscheme is the one you stop noticing.
Read the full reasoning in docs/philosophy.md.
Features
- Semantic-first — four roles, consistent across every language
- OKLCH color engine — perceptually uniform; adjustments look right
- Automatic contrast hierarchy — definition > constant > string > comment
- Dual palettes — independent dark and light variants, hand-tuned
- Deep plugin coverage — Telescope, nvim-cmp, Neo-tree, Gitsigns, Which-key, Trouble, Notify, and more
- Extension API — add highlight mappings for your own plugins without modifying core files
- Runtime commands — switch variants, preview, reload without restarting
- Built-in statusline — minimal, semantic, opt-in
- Health check —
:checkhealth aaltoreports contrast, gamut, and light/dark balance
Requirements
- Neovim 0.9+
- 24-bit color terminal or GUI
Installation
lazy.nvim
{
"micdzu/aalto.nvim",
priority = 1000,
config = function()
require("aalto").setup({})
vim.cmd("colorscheme aalto")
end,
}
packer.nvim
use {
"micdzu/aalto.nvim",
config = function()
require("aalto").setup({})
vim.cmd("colorscheme aalto")
end,
}
vim-plug
Plug 'micdzu/aalto.nvim'
require("aalto").setup({})
vim.cmd("colorscheme aalto")
Quick start
-- Dark variant, all defaults
require("aalto").setup({})
vim.cmd("colorscheme aalto")
-- Light variant
require("aalto").setup({ variant = "light" })
vim.cmd("colorscheme aalto")
Configuration
require("aalto").setup({
-- "dark" or "light"
variant = "dark",
-- Override raw palette hues. Contrast and hierarchy are re-applied on top.
palette = {
definition = "#7C8CFA",
string = "#8FC77C",
constant = "#B87EDC",
comment = "#746FA3",
},
-- Override semantic role colors directly, bypassing palette mapping.
semantic = {
definition = "#82AAFF",
},
-- Bold/italic for comments and keywords only.
styles = {
comments = { italic = true },
keywords = {}, -- e.g. { bold = true }
},
-- Transparent backgrounds.
transparent = false, -- main windows
float_transparent = false, -- floating windows
-- Set terminal_color_0 … terminal_color_15.
terminal_colors = true,
-- Applied last, wins over everything.
overrides = {
-- ["@keyword"] = { italic = true },
},
-- Enable built-in statusline.
statusline = false,
-- Print resolved palette after setup.
debug = false,
})
Full customization reference: docs/customization.md
Commands
| Command | Description |
|---|---|
:AaltoVariant [dark|light] |
Switch variant, or toggle if no argument |
:AaltoStatus |
Show current configuration |
:AaltoReload |
Re-apply highlights with the last used config |
:AaltoPreview dark|light |
Preview a variant temporarily without saving |
Lualine
require("lualine").setup({
options = {
theme = require("aalto").lualine_theme(),
},
})
Pass { lualine_style = "full" } for a filled mode indicator instead of the
default minimal one.
Custom plugin highlights
require("aalto").register_plugin_specs({
{
definition = { "MyPluginTitle", "MyPluginHeader" },
fg_dark = { "MyPluginBorder", "MyPluginSeparator" },
error = { "MyPluginErrorSign" },
string = { "MyPluginAddedLine" },
},
})
Available roles: definition, constant, string, comment, fg, fg_dark,
error, warn, info, hint, bg, bg_light, selection,
inv_definition, inv_constant, inv_string.
Full reference: docs/plugins.md
Health check
:checkhealth aalto
Reports contrast ratios, gamut warnings, and a light/dark comparison table.
Architecture
base → variants → semantic → link() → groups → highlights
- base — raw hues (dark/light palettes)
- variants — UI surfaces derived from background in OKLCH
- semantic — lightness positioning, chroma shaping, hierarchy enforcement
- link() — role-to-highlight-spec translation
- groups — Neovim highlight group definitions
Full internals: docs/design.md
License
MIT