feat: my own neovim theme!

This commit is contained in:
Stevan Freeborn
2026-06-30 15:07:14 -05:00
commit a9d19660da
11 changed files with 659 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 lavender-dimmed
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+93
View File
@@ -0,0 +1,93 @@
# lavender-dimmed.nvim
A muted lavender-themed colorscheme for Neovim, inspired by GitHub Dark Dimmed. Built with a soft purple accent palette that I've become quite enamored with.
## Install
### lazy.nvim
```lua
{
"StevanFreeborn/lavender-dimmed.nvim",
priority = 1000,
config = function()
require("lavender-dimmed").setup()
vim.cmd.colorscheme("lavender-dimmed")
end,
}
```
### vim-plug
```vim
Plug "StevanFreeborn/lavender-dimmed.nvim"
```
Then in your init.lua:
```lua
require("lavender-dimmed").setup()
vim.cmd.colorscheme("lavender-dimmed")
```
Or in init.vim:
```vim
colorscheme lavender-dimmed
```
### packer.nvim
```lua
use {
"StevanFreeborn/lavender-dimmed.nvim",
config = function()
require("lavender-dimmed").setup()
vim.cmd.colorscheme("lavender-dimmed")
end,
}
```
## Usage
```lua
-- Minimal
vim.cmd.colorscheme("lavender-dimmed")
-- With custom styles
require("lavender-dimmed").setup({
styles = {
comments = { italic = true },
keywords = { italic = false },
functions = {},
variables = {},
types = {},
numbers = {},
operators = {},
strings = {},
conditionals = {},
loops = {},
},
})
```
## Palette
| Color | Hex | Usage |
|---------------|-----------|------------------------|
| Lavender | `#b39cd0` | Strings, accent, menus |
| Deep lavender | `#a088c0` | Functions, titles |
| Teal | `#7ec8c0` | Types, tags, success |
| Gold | `#d4c87a` | Constants, operators |
| Coral | `#d47373` | Keywords, errors |
| Orange | `#cc6b2c` | Severe warnings |
| Cyan | `#76e3ea` | Regex, special strings |
| Pink | `#c96198` | Sponsors |
## Terminal Colors
Supports 16 ANSI terminal colors configured automatically on load.
## License
MIT
+1
View File
@@ -0,0 +1 @@
require("lavender-dimmed").load()
+22
View File
@@ -0,0 +1,22 @@
local M = {}
M.options = {
styles = {
comments = { italic = true },
keywords = { italic = false },
functions = {},
variables = {},
types = {},
numbers = {},
operators = {},
strings = {},
conditionals = {},
loops = {},
},
}
function M.setup(opts)
M.options = vim.tbl_deep_extend("force", M.options, opts or {})
end
return M
+95
View File
@@ -0,0 +1,95 @@
local function get(p)
return {
Normal = { fg = p.fg_bright, bg = p.bg },
NormalFloat = { fg = p.fg_bright, bg = p.bg_dark },
NormalNC = { fg = p.fg, bg = p.bg },
EndOfBuffer = { fg = p.bg },
Cursor = { fg = p.bg, bg = p.fg_bright },
lCursor = { link = "Cursor" },
CursorIM = { link = "Cursor" },
CursorLine = { bg = p.bg_highlight },
CursorColumn = { bg = p.bg_highlight },
CursorLineNr = { fg = p.gold },
LineNr = { fg = p.fg_muted2 },
SignColumn = { bg = p.bg },
FoldColumn = { fg = p.fg_subtle, bg = p.bg },
Folded = { fg = p.fg_muted, bg = p.bg_highlight },
Pmenu = { fg = p.fg, bg = p.bg_dark },
PmenuSel = { fg = p.fg_bright, bg = p.deep_lavender },
PmenuSbar = { bg = p.bg_visual },
PmenuThumb = { bg = p.fg_subtle },
PmenuKind = { fg = p.lavender },
PmenuKindSel = { fg = p.fg_white, bg = p.deep_lavender },
PmenuExtra = { fg = p.fg_muted },
PmenuExtraSel = { fg = p.fg_bright },
Search = { fg = p.bg, bg = p.gold },
IncSearch = { fg = p.bg, bg = p.bright_gold },
CurSearch = { fg = p.bg, bg = p.bright_gold },
Substitute = { fg = p.bg, bg = p.coral },
Visual = { bg = p.accent_subtle },
VisualNOS = { bg = p.accent_subtle },
MatchParen = { bg = p.accent_muted },
ColorColumn = { bg = p.bg_highlight },
Conceal = { fg = p.fg_subtle },
SpellBad = { sp = p.coral, undercurl = true },
SpellCap = { sp = p.gold, undercurl = true },
SpellLocal = { sp = p.teal, undercurl = true },
SpellRare = { sp = p.lavender, undercurl = true },
StatusLine = { fg = p.fg, bg = p.bg_highlight },
StatusLineNC = { fg = p.fg_muted, bg = p.bg },
TabLine = { fg = p.fg_muted, bg = p.bg },
TabLineFill = { bg = p.bg },
TabLineSel = { fg = p.fg_bright, bg = p.bg_highlight },
WinSeparator = { fg = p.bg_border },
VertSplit = { fg = p.bg_border },
FloatBorder = { fg = p.lavender, bg = p.bg_dark },
FloatTitle = { fg = p.lavender },
NonText = { fg = p.bg_visual },
Whitespace = { fg = p.bg_visual },
SpecialKey = { fg = p.fg_subtle },
Question = { fg = p.lavender },
Title = { fg = p.lavender },
WarningMsg = { fg = p.gold },
ErrorMsg = { fg = p.coral },
MoreMsg = { fg = p.teal },
ModeMsg = { fg = p.fg },
WildMenu = { fg = p.bg, bg = p.lavender },
QuickFixLine = { bg = p.accent_subtle },
DiffAdd = { bg = p.success_muted },
DiffDelete = { bg = p.danger_muted },
DiffChange = { bg = p.attention_muted },
DiffText = { bg = p.attention_muted },
diffAdded = { fg = p.teal },
diffRemoved = { fg = p.coral },
diffFile = { fg = p.lavender },
diffNewFile = { fg = p.teal },
diffLine = { fg = p.fg_muted },
diffIndexLine = { fg = p.fg_muted },
Directory = { fg = p.lavender },
Debug = { fg = p.coral },
DebugPC = { bg = p.accent_subtle },
DebugBreakpoint = { fg = p.bg, bg = p.coral },
}
end
return { get = get }
+80
View File
@@ -0,0 +1,80 @@
local function get(p)
return {
DiagnosticError = { fg = p.coral },
DiagnosticWarn = { fg = p.gold },
DiagnosticInfo = { fg = p.lavender },
DiagnosticHint = { fg = p.fg_muted },
DiagnosticOk = { fg = p.teal },
DiagnosticVirtualTextError = { fg = p.coral },
DiagnosticVirtualTextWarn = { fg = p.gold },
DiagnosticVirtualTextInfo = { fg = p.lavender },
DiagnosticVirtualTextHint = { fg = p.fg_muted },
DiagnosticVirtualTextOk = { fg = p.teal },
DiagnosticUnderlineError = { sp = p.coral, undercurl = true },
DiagnosticUnderlineWarn = { sp = p.gold, undercurl = true },
DiagnosticUnderlineInfo = { sp = p.lavender, undercurl = true },
DiagnosticUnderlineHint = { sp = p.fg_muted, undercurl = true },
DiagnosticUnderlineOk = { sp = p.teal, undercurl = true },
DiagnosticFloatingError = { fg = p.coral },
DiagnosticFloatingWarn = { fg = p.gold },
DiagnosticFloatingInfo = { fg = p.lavender },
DiagnosticFloatingHint = { fg = p.fg_muted },
DiagnosticSignError = { fg = p.coral },
DiagnosticSignWarn = { fg = p.gold },
DiagnosticSignInfo = { fg = p.lavender },
DiagnosticSignHint = { fg = p.fg_muted },
DiagnosticSignOk = { fg = p.teal },
DiagnosticDeprecated = { sp = p.coral, strikethrough = true },
DiagnosticUnnecessary = { fg = p.fg_muted2 },
LspReferenceText = { bg = p.accent_subtle },
LspReferenceRead = { bg = p.accent_subtle },
LspReferenceWrite = { bg = p.accent_subtle },
LspSignatureActiveParameter = { fg = p.gold, bold = true },
LspInlayHint = { fg = p.fg_muted2, bg = p.bg_highlight },
LspCodeLens = { fg = p.fg_muted },
LspCodeLensSeparator = { fg = p.fg_muted },
LspInfoBorder = { fg = p.lavender },
-- Legacy LSP diagnostic groups
LspDiagnosticsDefaultError = { fg = p.coral },
LspDiagnosticsDefaultWarning = { fg = p.gold },
LspDiagnosticsDefaultInformation = { fg = p.lavender },
LspDiagnosticsDefaultHint = { fg = p.fg_muted },
LspDiagnosticsDefaultOk = { fg = p.teal },
LspDiagnosticsVirtualTextError = { fg = p.coral },
LspDiagnosticsVirtualTextWarning = { fg = p.gold },
LspDiagnosticsVirtualTextInformation = { fg = p.lavender },
LspDiagnosticsVirtualTextHint = { fg = p.fg_muted },
LspDiagnosticsUnderlineError = { sp = p.coral, undercurl = true },
LspDiagnosticsUnderlineWarning = { sp = p.gold, undercurl = true },
LspDiagnosticsUnderlineInformation = { sp = p.lavender, undercurl = true },
LspDiagnosticsUnderlineHint = { sp = p.fg_muted, undercurl = true },
LspDiagnosticsFloatingError = { fg = p.coral },
LspDiagnosticsFloatingWarning = { fg = p.gold },
LspDiagnosticsFloatingInformation = { fg = p.lavender },
LspDiagnosticsFloatingHint = { fg = p.fg_muted },
LspDiagnosticsSignError = { fg = p.coral },
LspDiagnosticsSignWarning = { fg = p.gold },
LspDiagnosticsSignInformation = { fg = p.lavender },
LspDiagnosticsSignHint = { fg = p.fg_muted },
-- Terminal colors
Terminal = { bg = p.bg },
healthError = { fg = p.coral },
healthSuccess = { fg = p.teal },
healthWarning = { fg = p.gold },
}
end
return { get = get }
+62
View File
@@ -0,0 +1,62 @@
local function get(p, config)
local styles = config.styles
local function st(style_opts)
local result = {}
if style_opts then
for k, v in pairs(style_opts) do
result[k] = v
end
end
return result
end
return {
Comment = vim.tbl_extend("force", { fg = p.fg_muted2 }, st(styles.comments)),
Constant = { fg = p.gold },
String = { fg = p.lavender },
Character = { fg = p.lavender },
Number = { fg = p.gold },
Boolean = { fg = p.gold },
Float = { fg = p.gold },
Identifier = { fg = p.fg_bright },
Function = vim.tbl_extend("force", { fg = p.deep_lavender }, st(styles.functions)),
Statement = vim.tbl_extend("force", { fg = p.coral }, st(styles.keywords)),
Conditional = vim.tbl_extend("force", { fg = p.coral }, st(styles.conditionals)),
Repeat = vim.tbl_extend("force", { fg = p.coral }, st(styles.loops)),
Label = { fg = p.coral },
Operator = vim.tbl_extend("force", { fg = p.gold }, st(styles.operators)),
Keyword = vim.tbl_extend("force", { fg = p.coral }, st(styles.keywords)),
Exception = { fg = p.coral },
PreProc = { fg = p.coral },
Include = { fg = p.coral },
Define = { fg = p.coral },
Macro = { fg = p.coral },
PreCondit = { fg = p.coral },
Type = vim.tbl_extend("force", { fg = p.teal }, st(styles.types)),
StorageClass = { fg = p.teal },
Structure = { fg = p.teal },
Typedef = { fg = p.teal },
Special = { fg = p.fg_bright },
SpecialChar = { fg = p.fg_bright },
Tag = { fg = p.teal },
Delimiter = { fg = p.fg_dim },
SpecialComment = { fg = p.fg_muted },
Underlined = { underline = true },
Bold = { bold = true },
Italic = { italic = true },
Error = { fg = p.coral },
Todo = { fg = p.gold, bg = p.bg },
}
end
return { get = get }
+130
View File
@@ -0,0 +1,130 @@
local function get(p, config)
local styles = config.styles
return {
["@comment"] = { fg = p.fg_muted2, italic = true },
["@comment.error"] = { fg = p.coral },
["@comment.warning"] = { fg = p.gold },
["@comment.todo"] = { fg = p.gold, bold = true },
["@comment.note"] = { fg = p.lavender, bold = true },
["@comment.hint"] = { fg = p.teal },
["@constant"] = { fg = p.gold },
["@constant.builtin"] = { fg = p.gold },
["@constant.macro"] = { fg = p.gold },
["@define"] = { fg = p.gold },
["@string"] = { fg = p.lavender },
["@string.documentation"] = { fg = p.lavender },
["@string.regexp"] = { fg = p.cyan },
["@string.escape"] = { fg = p.teal },
["@string.special"] = { fg = p.teal },
["@character"] = { fg = p.lavender },
["@character.special"] = { fg = p.teal },
["@number"] = { fg = p.gold },
["@number.float"] = { fg = p.gold },
["@boolean"] = { fg = p.gold },
["@function"] = { fg = p.deep_lavender },
["@function.builtin"] = { fg = p.gold },
["@function.call"] = { fg = p.deep_lavender },
["@function.macro"] = { fg = p.gold },
["@function.method"] = { fg = p.deep_lavender },
["@function.method.call"] = { fg = p.deep_lavender },
["@parameter"] = { fg = p.fg_bright },
["@parameter.reference"] = { fg = p.fg_bright },
["@field"] = { fg = p.fg_bright },
["@property"] = { fg = p.fg_bright },
["@symbol"] = { fg = p.fg_bright },
["@variable"] = { fg = p.fg_bright },
["@variable.builtin"] = { fg = p.coral },
["@variable.member"] = { fg = p.fg_bright },
["@variable.parameter"] = { fg = p.fg_bright },
["@type"] = { fg = p.teal },
["@type.builtin"] = { fg = p.teal },
["@type.definition"] = { fg = p.teal },
["@type.qualifier"] = { fg = p.teal },
["@keyword"] = { fg = p.coral },
["@keyword.function"] = { fg = p.coral },
["@keyword.return"] = { fg = p.coral },
["@keyword.operator"] = { fg = p.coral },
["@keyword.exception"] = { fg = p.coral },
["@keyword.modifier"] = { fg = p.coral },
["@keyword.repeat"] = { fg = p.coral },
["@keyword.storage"] = { fg = p.teal },
["@keyword.conditional"] = { fg = p.coral },
["@keyword.directive"] = { fg = p.coral },
["@keyword.directive.define"] = { fg = p.coral },
["@conditional"] = { fg = p.coral },
["@repeat"] = { fg = p.coral },
["@debug"] = { fg = p.coral },
["@label"] = { fg = p.fg_bright },
["@operator"] = { fg = p.gold },
["@punctuation.delimiter"] = { fg = p.fg_dim },
["@punctuation.bracket"] = { fg = p.fg },
["@punctuation.special"] = { fg = p.coral },
["@preproc"] = { fg = p.coral },
["@include"] = { fg = p.coral },
["@attribute"] = { fg = p.teal },
["@tag"] = { fg = p.teal },
["@tag.attribute"] = { fg = p.fg_bright },
["@tag.delimiter"] = { fg = p.fg_muted },
["@namespace"] = { fg = p.teal },
["@module"] = { fg = p.deep_lavender },
["@text"] = { fg = p.fg_bright },
["@text.strong"] = { bold = true },
["@text.emphasis"] = { italic = true },
["@text.underline"] = { underline = true },
["@text.strike"] = { strikethrough = true },
["@text.title"] = { fg = p.deep_lavender },
["@text.literal"] = { fg = p.lavender },
["@text.quote"] = { fg = p.fg_muted },
["@text.note"] = { fg = p.lavender },
["@text.warning"] = { fg = p.gold },
["@text.danger"] = { fg = p.coral },
["@text.diff.add"] = { fg = p.teal },
["@text.diff.delete"] = { fg = p.coral },
["@markup.heading"] = { fg = p.deep_lavender },
["@markup.heading.1"] = { fg = p.coral },
["@markup.heading.2"] = { fg = p.gold },
["@markup.heading.3"] = { fg = p.teal },
["@markup.heading.4"] = { fg = p.lavender },
["@markup.heading.5"] = { fg = p.deep_lavender },
["@markup.heading.6"] = { fg = p.lavender },
["@markup.list"] = { fg = p.fg_bright },
["@markup.list.checked"] = { fg = p.teal },
["@markup.list.unchecked"] = { fg = p.fg_muted },
["@markup.link"] = { fg = p.lavender, underline = true },
["@markup.link.url"] = { fg = p.lavender, underline = true },
["@markup.link.label"] = { fg = p.fg_bright },
["@markup.raw"] = { fg = p.fg_bright },
["@markup.raw.block"] = { fg = p.fg_bright },
["@markup.code"] = { fg = p.fg_bright },
["@markup.math"] = { fg = p.cyan },
["@markup.environment"] = { fg = p.fg_bright },
["@markup.environment.name"] = { fg = p.teal },
["@diff.plus"] = { fg = p.teal },
["@diff.minus"] = { fg = p.coral },
["@diff.delta"] = { fg = p.gold },
["@none"] = {},
}
end
return { get = get }
+56
View File
@@ -0,0 +1,56 @@
local M = {}
local did_setup = false
function M.setup(opts)
require("lavender-dimmed.config").setup(opts)
did_setup = true
end
function M.load()
if not did_setup then
M.setup()
end
local config = require("lavender-dimmed.config")
local palette = require("lavender-dimmed.palette").load()
local groups = {}
local group_modules = { "editor", "syntax", "treesitter", "lsp" }
for _, name in ipairs(group_modules) do
local g = require("lavender-dimmed.groups." .. name).get(palette, config.options)
for k, v in pairs(g) do
groups[k] = v
end
end
for group_name, group_opts in pairs(groups) do
vim.api.nvim_set_hl(0, group_name, group_opts)
end
local term_colors = {
"#282c34",
"#d47373",
"#7ec8c0",
"#d4c87a",
"#b39cd0",
"#a088c0",
"#76e3ea",
"#cdd9e5",
"#373e47",
"#e08585",
"#8dd8d0",
"#ddd491",
"#c8b8e0",
"#b39cd0",
"#b3f0ff",
"#e0e4e8",
}
for i, color in ipairs(term_colors) do
vim.g["terminal_color_" .. (i - 1)] = color
end
end
return M
+47
View File
@@ -0,0 +1,47 @@
local Color = {}
Color.__index = Color
function Color.new(hex)
local self = setmetatable({}, Color)
hex = hex:gsub("^#", "")
self.r = tonumber(hex:sub(1, 2), 16)
self.g = tonumber(hex:sub(3, 4), 16)
self.b = tonumber(hex:sub(5, 6), 16)
return self
end
function Color:to_css()
return string.format("#%02x%02x%02x", self.r, self.g, self.b)
end
function Color:blend(other, factor)
other = type(other) == "string" and Color.new(other) or other
return Color.new(
string.format(
"#%02x%02x%02x",
math.floor(self.r + (other.r - self.r) * factor + 0.5),
math.floor(self.g + (other.g - self.g) * factor + 0.5),
math.floor(self.b + (other.b - self.b) * factor + 0.5)
)
)
end
function Color:darken(amount)
return self:blend("#000000", amount)
end
function Color:lighten(amount)
return self:blend("#ffffff", amount)
end
function Color:saturate(factor)
local gray = math.floor(0.299 * self.r + 0.587 * self.g + 0.114 * self.b + 0.5)
local gray_color = Color.new(string.format("#%02x%02x%02x", gray, gray, gray))
return self:blend(gray_color, 1 - factor)
end
return setmetatable(Color, {
__call = function(_, hex)
return Color.new(hex)
end,
})
+52
View File
@@ -0,0 +1,52 @@
local M = {}
function M.load()
return {
terminal_bg = "#282c34",
bg_dark = "#1c2128",
bg_dimmer = "#22272e",
bg = "#282c34",
bg_highlight = "#2d333b",
bg_visual = "#373e47",
bg_border = "#444c56",
fg_subtle = "#545d68",
fg_muted2 = "#636e7b",
fg_muted = "#768390",
fg_dim = "#909dab",
fg = "#adbac7",
fg_bright = "#cdd9e5",
fg_white = "#e0e4e8",
lavender = "#b39cd0",
deep_lavender = "#a088c0",
bright_lavender = "#c8b8e0",
teal = "#7ec8c0",
bright_teal = "#8dd8d0",
gold = "#d4c87a",
bright_gold = "#ddd491",
coral = "#d47373",
bright_coral = "#e08585",
orange = "#cc6b2c",
cyan = "#76e3ea",
bright_cyan = "#b3f0ff",
pink = "#c96198",
purple = "#986ee2",
accent_muted = "#605972",
accent_subtle = "#3d3d4b",
success_muted = "#4a6a6c",
success_subtle = "#354349",
danger_muted = "#6d484d",
danger_subtle = "#42373d",
attention_muted = "#6d6a50",
attention_subtle = "#42433f",
}
end
return M