I spend most of my day in two places: Ghostty (terminal) and Neovim (editor, running inside Ghostty). For years they disagreed with each other. Ghostty used one palette, Neovim used a different one, and my eyes kept re-adapting every time I switched between a shell and a buffer — even though they were technically in the same window.
I finally got tired of it and built citruszest-ghostty.nvim — a Neovim colorscheme that shares its 16 terminal colors exactly with Ghostty. Same hex values, same semantics, one palette.
The Palette
Inspired by citruszest.nvim and Ghostty’s default colors, with a bright citrus bias:
bg = "#121212" -- deep charcoal
fg = "#bfbfbf" -- comfortable light gray
fg_bright = "#f9f9f9"
red = "#ff5454" -- errors, exceptions
red_bright = "#ff1a75" -- statements, keywords
green = "#00cc7a" -- strings, success
green_bright = "#1affa3" -- escape chars, special
yellow = "#ffd400" -- types, titles, warnings
yellow_bright = "#ffff00"
blue = "#00bfff" -- functions, links
blue_bright = "#33cfff" -- built-in functions, modules
magenta = "#ff90fe" -- preprocessor, imports
magenta_bright = "#ffb2fe" -- macros
cyan = "#48d1cc" -- operators, properties
cyan_bright = "#00fff2" -- regex, special chars
orange = "#ff8c00" -- constants, numbers, search
cursor = "#00e086" -- mint, always visible
selection_bg = "#ff8c00" -- orange, high-contrast selection
The ground is #121212 — deeper than most “dark” palettes but not pure black, so cursors and selections read cleanly without halation.
The signature move is orange as the selection background. Most colorschemes use a muted gray or a tinted-blue selection, which means selected text fades into the ground. Orange selection is unmissable — when you visual-select a block, you know exactly what you’ve got.
Why Share a Palette Between Terminal and Editor?
Three reasons:
1. Eye adaptation. Every time your terminal’s green is #4caf50 and your Neovim’s green is #98c379, your eye re-tunes. Over hours that’s real cognitive load. When both use #00cc7a, your visual system only ever learns one green.
2. Copy-paste fidelity. Shell output piped into Neovim (git status in a buffer, error logs opened in a split) keeps the same colors it had in the terminal. A red error stays the same red. A green “Added” stays the same green.
3. Consistent mental model. In citruszest-ghostty, red means error. Everywhere. Terminal errors, LSP diagnostics, TSX lint squiggles, shell exit codes. One semantics, 16 colors.
The Neovim Side
The plugin is small — under 300 lines of Lua covering:
- Neovim core groups: Normal, Comment, String, Function, Type, Keyword, etc.
- Treesitter: full
@variable,@function,@keyword.*,@markup.*,@tag.*coverage - LSP semantic tokens:
@lsp.type.class,@lsp.type.function,@lsp.type.enumMember, etc. linked to the Treesitter equivalents so language servers that emit semantic tokens get the same colors as the parser - Diagnostics: error/warn/info/hint with subtle tinted-background virtual text, so inline diagnostics read without stealing focus from your code
- Plugin support: Telescope, Snacks picker, Lazy, Which-key, Noice, GitSigns, Mini, IndentBlankline, Treesitter-context
- Terminal colors:
vim.g.terminal_color_0throughterminal_color_15set from the palette so:terminside Neovim matches Ghostty’s shell colors exactly
The vim.g.terminal_color_* assignment is the detail that closes the loop: when you open a terminal inside Neovim, the shell inherits the same 16 colors the editor is using, so ls --color, git status, and rg all render with the same greens, blues, and oranges you see in your source files.
Shipping It
{
"jeryldev/citruszest-ghostty.nvim",
lazy = false,
priority = 1000,
}
Then in your LazyVim config:
{
"LazyVim/LazyVim",
opts = {
colorscheme = "citruszest-ghostty",
},
}
The plugin ships colorscheme citruszest-ghostty as the main command, plus palette as an importable module so other plugins or user config can reach into the hex values directly:
local p = require("citruszest-ghostty.palette")
vim.api.nvim_set_hl(0, "MyCustom", { fg = p.orange, bold = true })
Design Notes
A few choices worth calling out:
Italics on keywords, not bold. Keyword, @keyword, @keyword.return, @variable.parameter are italic. Bold is reserved for @function, @constant.builtin, @type.builtin, and Boolean — things that are named entities, not syntactic scaffolding. Keywords are structural; they deserve visual distinction but not visual weight.
Operators in cyan. +, -, ->, =>, :: all read as #48d1cc. In a dense Elixir or TypeScript buffer, this means operators “recede” into the background of the syntax — you see the names first, operators second. That’s the right reading order for most languages.
Diagnostics get tinted backgrounds. Virtual text for DiagnosticVirtualTextError uses #2e1a1a as its background — a dark red-tinted ink, not a red highlight. It makes the inline error readable without making it shout.
Yellow is reserved for types. Types, type definitions, @constructor, @module (the capital-N namespace) — all yellow. This makes the type system visually scannable in statically-typed buffers without being aggressive about it.
Where It Fits
This is a small plugin that solves a small problem. Most of the work is in the 200+ highlight group assignments; the hard part is taste, not code. I’ve been using it as my daily driver since late February and the palette has stayed stable enough that I felt comfortable making the repo public.
If you use Ghostty and Neovim together and the color mismatch has been bothering you, give it a try. If you use a different terminal, the palette still works — the colors are designed to be readable on #121212, not specifically tied to Ghostty’s renderer.
The palette is MIT-licensed and the whole plugin is open source. PRs welcome, especially for plugin support I haven’t added yet.
The #00cc7a mint green in this palette is the same accent you see across this site. Portfolio and editor use the same palette for the same reason terminals and editors should: one visual system, one set of meanings.