← Back to DevBytes

Neovim Themes and Customization: Complete Guide

Understanding Neovim Themes and Customization

Neovim themes encompass far more than simple color schemes. They represent the complete visual layer of your editorβ€”colorschemes, statusline designs, tabline configurations, buffer indicators, icon sets, and even UI element styling. Customization in Neovim means shaping every pixel of your editing environment to match your workflow, aesthetic preferences, and productivity needs. Unlike traditional Vim where customization was limited to a handful of highlight groups, Neovim's modern ecosystemβ€”powered by Lua, treesitter, and robust plugin APIsβ€”enables deep, granular control over every visual element.

Why Visual Customization Matters for Developers

A well-customized editor delivers concrete benefits that go beyond mere aesthetics:

How Neovim Handles Themes: The Architecture

Before diving into practical customization, understanding the underlying architecture prevents confusion. Neovim's visual system is built on three interconnected layers:

1. Highlight Groups

Highlight groups are named collections of style attributesβ€”foreground color, background color, bold, italic, underline, and blend parameters. Every piece of text you see in Neovim is rendered through a highlight group. The built-in groups include Normal, Comment, String, Function, Keyword, and dozens more. Treesitter adds semantic groups like @function, @type, and @variable. You can inspect any group with:

# From within Neovim, show the current highlight group under cursor
:InspectHighlight

# Or to see all defined groups with their colors
:highlight

2. Colorschemes

A colorscheme is a Lua or Vimscript file that defines the color values and style attributes for every highlight group. Modern Neovim colorschemes use Lua exclusively and often leverage treesitter highlight groups for semantic precision. Colorschemes can be loaded with the vim.cmd.colorscheme command or through plugin managers. The best modern colorschemesβ€”such as tokyonight, catppuccin, rose-pine, gruvbox-material, and nightfoxβ€”provide extensive configuration options for transparency, brightness, and style variants.

3. UI Plugins and Component Libraries

Beyond colorschemes, dedicated UI plugins handle specific visual components. The most common are:

These components each maintain their own highlight groups and configuration, allowing granular control independent of the base colorscheme.

Practical Setup: Installing and Switching Themes

Modern Neovim configuration typically uses a plugin manager. Below is a complete, production-ready example using lazy.nvimβ€”the most popular choice for its lazy-loading capabilities and clean API. This configuration installs three complementary pieces: a colorscheme, a statusline, and a bufferline.

-- ~/.config/nvim/lua/plugins/appearance.lua
-- A unified appearance configuration file

return {
  -- === COLORSCHEME ===
  {
    "folke/tokyonight.nvim",
    lazy = false,          -- Load immediately on startup
    priority = 1000,       -- High priority to load before other plugins
    opts = {
      style = "night",     -- Options: "night", "storm", "day", "moon"
      transparent = false,
      terminal_colors = true,
      styles = {
        comments = { italic = true },
        keywords = { italic = false },
        functions = { bold = true },
        variables = { italic = false },
      },
      sidebars = { "qf", "vista", "spectre", "toggleterm", "packer" },
      hide_sidebar_background = false,
      day_brightness = 0.3,
      dim_inactive = false,
      lualine_bold = true,
    },
    config = function(_, opts)
      require("tokyonight").setup(opts)
      -- Apply the colorscheme immediately
      vim.cmd.colorscheme("tokyonight")
      -- Enable terminal true color support
      vim.o.termguicolors = true
    end,
  },

  -- === STATUSLINE ===
  {
    "nvim-lualine/lualine.nvim",
    dependencies = { "nvim-tree/nvim-web-devicons" },
    opts = {
      options = {
        icons_enabled = true,
        theme = "auto",              -- Follows your colorscheme
        component_separators = { left = "ξ‚±", right = "ξ‚³" },
        section_separators = { left = "ξ‚΄", right = "ξ‚Ά" },
        disabled_filetypes = {
          statusline = { "alpha", "dashboard", "NvimTree" },
        },
        always_divide_middle = true,
        globalstatus = false,
        refresh = {
          statusline = 100,
        },
      },
      sections = {
        lualine_a = { "mode" },
        lualine_b = { "branch", "diff", "diagnostics" },
        lualine_c = {
          {
            "filename",
            file_status = true,
            path = 1,  -- 0 = filename, 1 = relative, 2 = absolute, 3 = shorten
          },
        },
        lualine_x = {
          "encoding",
          "fileformat",
          "filetype",
        },
        lualine_y = { "progress" },
        lualine_z = { "location" },
      },
      inactive_sections = {
        lualine_c = { "filename" },
        lualine_x = { "location" },
      },
    },
  },

  -- === BUFFERLINE (Tabline) ===
  {
    "akinsho/bufferline.nvim",
    dependencies = { "nvim-tree/nvim-web-devicons" },
    opts = {
      options = {
        mode = "buffers",        -- "buffers" or "tabs"
        numbers = "none",        -- "none", "ordinal", "buffer_id", "both"
        close_command = "Bdelete! %d",
        right_mouse_command = "vertical resize",
        indicator = {
          icon = "β–Ž",
          style = "icon",
        },
        buffer_close_icon = "ο™•",
        modified_icon = "●",
        close_icon = "",
        left_trunc_marker = "",
        right_trunc_marker = "ο‚©",
        max_name_length = 18,
        max_prefix_length = 15,
        tab_size = 18,
        diagnostics = "nvim_lsp",
        diagnostics_indicator = function(_, _, diagnostics_dict)
          local s = ""
          for e, n in pairs(diagnostics_dict) do
            local sym = e == "error" and " "
              or (e == "warning" and " " or " ")
            s = s .. sym .. n
          end
          return s
        end,
        offsets = {
          {
            filetype = "NvimTree",
            text = "File Explorer",
            highlight = "Directory",
            separator = true,
          },
        },
        show_buffer_icons = true,
        show_buffer_close_icons = true,
        show_close_icon = true,
        show_tab_indicators = true,
        persist_buffer_sort = true,
        separator_style = "slant",
        enforce_regular_tabs = false,
        always_show_bufferline = true,
        sort_by = "insert_ending",
      },
    },
  },

  -- === INDENT GUIDES ===
  {
    "lukas-reineke/indent-blankline.nvim",
    main = "ibl",
    opts = {
      scope = {
        enabled = true,
        show_start = true,
        show_end = false,
        highlight = { "Function", "Label" },
      },
      indent = {
        char = "▏",
        tab_char = "▏",
      },
    },
  },

  -- === DASHBOARD (Startup Screen) ===
  {
    "goolord/alpha-nvim",
    dependencies = { "nvim-tree/nvim-web-devicons" },
    config = function()
      local alpha = require("alpha")
      local dashboard = require("alpha.themes.dashboard")
      
      dashboard.section.header.val = {
        "                                                     ",
        "  β–ˆβ–ˆβ–ˆβ•—   β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—   β–ˆβ–ˆβ•—β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•—   β–ˆβ–ˆβ–ˆβ•— ",
        "  β–ˆβ–ˆβ–ˆβ–ˆβ•—  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘ ",
        "  β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—  β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘ ",
        "  β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•  β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘ ",
        "  β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘ ",
        "  β•šβ•β•  β•šβ•β•β•β•β•šβ•β•β•β•β•β•β• β•šβ•β•β•β•β•β•   β•šβ•β•β•β•  β•šβ•β•β•šβ•β•     β•šβ•β• ",
        "                                                     ",
      }
      
      dashboard.section.buttons.val = {
        dashboard.button("f", "  Find file", ":Telescope find_files "),
        dashboard.button("e", "ο…›  New file", ":enei  startinsert "),
        dashboard.button("p", "  Find project", ":Telescope projects "),
        dashboard.button("r", "  Recent files", ":Telescope oldfiles "),
        dashboard.button("t", "οžƒ  Find text", ":Telescope live_grep "),
        dashboard.button("c", "ο™Ÿ  Configuration", ":e ~/.config/nvim/lua/ "),
        dashboard.button("q", "ο™™  Quit Neovim", ":qa"),
      }
      
      dashboard.section.footer.val = "Happy coding!"
      
      alpha.setup(dashboard.opts)
      
      vim.cmd([[
        autocmd User AlphaReady set showtabline=0 | autocmd BufUnload * set showtabline=2
      ]])
    end,
  },
}

This single file gives you a fully themed, visually cohesive environment. Each plugin's options are self-documenting; adjust the tables to taste.

Creating Custom Highlights That Override Your Colorscheme

Sometimes you want to tweak specific elements without switching colorschemes entirely. Neovim provides the vim.api.nvim_set_hl function for this purpose. Here's how to override highlight groups after your colorscheme has loaded:

-- ~/.config/nvim/lua/config/highlights.lua
-- Custom highlight overrides applied after colorscheme loads

local function set_highlights()
  local highlights = {
    -- Make comments more visible with a brighter color and italics
    Comment = { fg = "#7aa2f7", italic = true },

    -- Customize the cursor line to be subtle
    CursorLine = { bg = "#2f353b" },
    CursorLineNr = { fg = "#ff9e64", bold = true },

    -- Make diagnostic underlines more subtle
    DiagnosticUnderlineError = { sp = "#f7768e", undercurl = true },
    DiagnosticUnderlineWarn  = { sp = "#e0af68", undercurl = true },
    DiagnosticUnderlineInfo  = { sp = "#0db9d7", undercurl = true },
    DiagnosticUnderlineHint  = { sp = "#9ece6a", undercurl = true },

    -- Customize floating windows (LSP hover, completion docs)
    NormalFloat = { bg = "#1e222a" },
    FloatBorder = { fg = "#3b4261", bg = "#1e222a" },
    FloatTitle   = { fg = "#7aa2f7", bg = "#1e222a", bold = true },

    -- Make the statusline background distinct
    StatusLine   = { bg = "#3b4261", fg = "#c0caf5" },
    StatusLineNC = { bg = "#1a1b26", fg = "#565f89" },

    -- Improve visual selection readability
    Visual       = { bg = "#364a82" },
    VisualNOS    = { bg = "#364a82" },

    -- Search highlights
    Search       = { bg = "#7aa2f7", fg = "#1a1b26", bold = true },
    IncSearch    = { bg = "#ff9e64", fg = "#1a1b26", bold = true },

    -- Treesitter semantic highlights
    ["@function"]        = { fg = "#7aa2f7", bold = true },
    ["@method"]          = { fg = "#7aa2f7" },
    ["@property"]        = { fg = "#7aa2f7" },
    ["@variable"]        = { fg = "#c0caf5" },
    ["@parameter"]       = { fg = "#e0af68", italic = true },
    ["@type"]            = { fg = "#2ac3de" },
    ["@keyword"]         = { fg = "#bb9af7", bold = true },
    ["@string"]          = { fg = "#9ece6a" },
    ["@number"]          = { fg = "#ff9e64" },
    ["@boolean"]         = { fg = "#ff9e64", bold = true },
    ["@constant"]        = { fg = "#ff9e64" },
    ["@namespace"]       = { fg = "#bb9af7" },
    ["@tag"]             = { fg = "#f7768e" },
    ["@tag.attribute"]   = { fg = "#7aa2f7" },
    ["@tag.delimiter"]   = { fg = "#565f89" },
  }

  -- Apply all highlights using the Neovim API
  for group, settings in pairs(highlights) do
    vim.api.nvim_set_hl(0, group, settings)
  end

  -- You can also use the shorthand with hex colors and styles
  -- vim.api.nvim_set_hl expects: { fg, bg, sp, bold, italic, underline, undercurl, ... }
end

-- Schedule this to run after colorscheme is fully loaded
vim.schedule(set_highlights)

-- Or use an autocmd to ensure it runs at the right time
vim.api.nvim_create_autocmd("ColorScheme", {
  pattern = "*",
  callback = function()
    vim.schedule(set_highlights)
  end,
})

The vim.schedule wrapper is critical hereβ€”it defers execution until after the colorscheme has fully applied its own highlights, preventing your overrides from being immediately overwritten.

Building Your Own Custom Colorscheme from Scratch

For developers who want complete control, creating a custom colorscheme is the ultimate expression of personalization. A colorscheme is simply a Lua file that defines terminal colors and highlight groups. Below is a minimal but complete example that you can expand into a full theme:

-- ~/.config/nvim/colors/mytheme.lua
-- A minimal custom colorscheme example
-- Load with: :colorscheme mytheme

-- Define your palette
local palette = {
  bg         = "#1a1b26",  -- Deep dark background
  bg_alt     = "#1e2030",  -- Slightly lighter background
  fg         = "#c0caf5",  -- Primary foreground
  fg_dim     = "#565f89",  -- Dimmed foreground
  red        = "#f7768e",
  orange     = "#ff9e64",
  yellow     = "#e0af68",
  green      = "#9ece6a",
  cyan       = "#2ac3de",
  blue       = "#7aa2f7",
  purple     = "#bb9af7",
  magenta    = "#ad8ee6",
  white      = "#ffffff",
  black      = "#000000",
  -- UI-specific colors
  comment    = "#565f89",
  selection  = "#364a82",
  line_hl    = "#2f353b",
  gutter_fg  = "#4c526b",
}

-- Helper function to create highlight definitions
local function hi(group, opts)
  local style = {}
  if opts.fg then style.fg = opts.fg end
  if opts.bg then style.bg = opts.bg end
  if opts.sp then style.sp = opts.sp end
  if opts.bold then style.bold = true end
  if opts.italic then style.italic = true end
  if opts.underline then style.underline = true end
  if opts.undercurl then style.undercurl = true end
  if opts.reverse then style.reverse = true end
  vim.api.nvim_set_hl(0, group, style)
end

-- Terminal colors (for :terminal buffers)
vim.g.terminal_color_0  = palette.black
vim.g.terminal_color_1  = palette.red
vim.g.terminal_color_2  = palette.green
vim.g.terminal_color_3  = palette.yellow
vim.g.terminal_color_4  = palette.blue
vim.g.terminal_color_5  = palette.purple
vim.g.terminal_color_6  = palette.cyan
vim.g.terminal_color_7  = palette.white
vim.g.terminal_color_8  = palette.fg_dim
vim.g.terminal_color_9  = palette.red
vim.g.terminal_color_10 = palette.green
vim.g.terminal_color_11 = palette.yellow
vim.g.terminal_color_12 = palette.blue
vim.g.terminal_color_13 = palette.purple
vim.g.terminal_color_14 = palette.cyan
vim.g.terminal_color_15 = palette.white

-- Base editor highlights
hi("Normal",       { fg = palette.fg,    bg = palette.bg })
hi("Comment",      { fg = palette.comment, italic = true })
hi("Constant",     { fg = palette.orange })
hi("String",       { fg = palette.green })
hi("Character",    { fg = palette.green })
hi("Number",       { fg = palette.orange })
hi("Boolean",      { fg = palette.orange, bold = true })
hi("Float",        { fg = palette.orange })

hi("Identifier",   { fg = palette.fg })
hi("Function",     { fg = palette.blue, bold = true })
hi("Statement",    { fg = palette.purple, bold = true })
hi("Conditional",  { fg = palette.purple, bold = true })
hi("Repeat",       { fg = palette.purple, bold = true })
hi("Label",        { fg = palette.blue })
hi("Operator",     { fg = palette.cyan })
hi("Keyword",      { fg = palette.purple, bold = true })
hi("Exception",    { fg = palette.purple, bold = true })

hi("PreProc",      { fg = palette.cyan })
hi("Include",      { fg = palette.purple })
hi("Define",       { fg = palette.purple })
hi("Macro",        { fg = palette.purple })
hi("PreCondit",    { fg = palette.cyan })

hi("Type",         { fg = palette.cyan, bold = true })
hi("StorageClass", { fg = palette.cyan, bold = true })
hi("Structure",    { fg = palette.cyan })
hi("Typedef",      { fg = palette.cyan, bold = true })

hi("Special",      { fg = palette.magenta })
hi("SpecialChar",  { fg = palette.magenta })
hi("Tag",          { fg = palette.red })
hi("Delimiter",    { fg = palette.fg_dim })
hi("SpecialComment", { fg = palette.comment })
hi("Debug",        { fg = palette.yellow })

-- UI highlights
hi("ColorColumn",   { bg = palette.line_hl })
hi("CursorColumn",  { bg = palette.line_hl })
hi("CursorLine",    { bg = palette.line_hl })
hi("CursorLineNr",  { fg = palette.yellow, bold = true })
hi("LineNr",        { fg = palette.gutter_fg })
hi("SignColumn",    { bg = palette.bg, fg = palette.gutter_fg })
hi("FoldColumn",    { bg = palette.bg, fg = palette.gutter_fg })
hi("Folded",        { fg = palette.comment, bg = palette.bg_alt })

hi("Directory",     { fg = palette.blue })
hi("Search",        { fg = palette.bg, bg = palette.yellow, bold = true })
hi("IncSearch",     { fg = palette.bg, bg = palette.orange, bold = true })
hi("Substitute",    { fg = palette.bg, bg = palette.green })
hi("MatchParen",    { bg = palette.selection, bold = true })

hi("Visual",        { bg = palette.selection })
hi("VisualNOS",     { bg = palette.selection })

hi("ErrorMsg",      { fg = palette.red, bold = true })
hi("WarningMsg",    { fg = palette.yellow, bold = true })
hi("ModeMsg",       { fg = palette.fg })
hi("MoreMsg",       { fg = palette.blue })
hi("Question",      { fg = palette.green })

hi("Title",         { fg = palette.blue, bold = true })
hi("NonText",       { fg = palette.gutter_fg })
hi("SpecialKey",    { fg = palette.gutter_fg })
hi("Whitespace",    { fg = palette.gutter_fg })

-- Pmenu (completion popup)
hi("Pmenu",         { fg = palette.fg, bg = palette.bg_alt })
hi("PmenuSel",      { fg = palette.bg, bg = palette.blue, bold = true })
hi("PmenuSbar",     { bg = palette.bg_alt })
hi("PmenuThumb",    { bg = palette.gutter_fg })

-- Floating windows
hi("NormalFloat",   { fg = palette.fg, bg = palette.bg_alt })
hi("FloatBorder",   { fg = palette.gutter_fg, bg = palette.bg_alt })
hi("FloatTitle",    { fg = palette.blue, bg = palette.bg_alt, bold = true })

-- Statusline
hi("StatusLine",    { fg = palette.fg, bg = palette.bg_alt })
hi("StatusLineNC",  { fg = palette.comment, bg = palette.bg_alt })

-- Tabline
hi("TabLine",       { fg = palette.comment, bg = palette.bg })
hi("TabLineFill",   { bg = palette.bg })
hi("TabLineSel",    { fg = palette.fg, bg = palette.bg_alt, bold = true })

-- Diagnostic highlights
hi("DiagnosticError",       { fg = palette.red })
hi("DiagnosticWarn",        { fg = palette.yellow })
hi("DiagnosticInfo",        { fg = palette.cyan })
hi("DiagnosticHint",        { fg = palette.green })
hi("DiagnosticUnderlineError", { sp = palette.red, undercurl = true })
hi("DiagnosticUnderlineWarn",  { sp = palette.yellow, undercurl = true })
hi("DiagnosticUnderlineInfo",  { sp = palette.cyan, undercurl = true })
hi("DiagnosticUnderlineHint",  { sp = palette.green, undercurl = true })

-- Git signs (gitsigns.nvim)
hi("GitSignsAdd",    { fg = palette.green })
hi("GitSignsChange", { fg = palette.yellow })
hi("GitSignsDelete", { fg = palette.red })

-- LSP references
hi("LspReferenceRead",   { bg = palette.selection })
hi("LspReferenceWrite",  { bg = palette.selection })
hi("LspReferenceText",   { bg = palette.selection })

-- Treesitter semantic highlights (requires nvim-treesitter)
-- The @ groups provide much richer semantic understanding
hi("@function",        { fg = palette.blue, bold = true })
hi("@method",          { fg = palette.blue })
hi("@property",        { fg = palette.blue })
hi("@variable",        { fg = palette.fg })
hi("@variable.builtin", { fg = palette.red })
hi("@parameter",       { fg = palette.yellow, italic = true })
hi("@type",            { fg = palette.cyan, bold = true })
hi("@type.builtin",    { fg = palette.cyan })
hi("@keyword",         { fg = palette.purple, bold = true })
hi("@keyword.function", { fg = palette.purple })
hi("@string",          { fg = palette.green })
hi("@string.regex",    { fg = palette.orange })
hi("@string.escape",   { fg = palette.magenta })
hi("@number",          { fg = palette.orange })
hi("@boolean",         { fg = palette.orange, bold = true })
hi("@constant",        { fg = palette.orange })
hi("@constant.builtin", { fg = palette.orange })
hi("@namespace",       { fg = palette.purple })
hi("@tag",             { fg = palette.red })
hi("@tag.attribute",   { fg = palette.blue })
hi("@tag.delimiter",   { fg = palette.fg_dim })
hi("@comment",         { fg = palette.comment, italic = true })
hi("@punctuation",     { fg = palette.fg_dim })
hi("@operator",        { fg = palette.cyan })
hi("@conditional",     { fg = palette.purple, bold = true })

-- Clear existing highlights and apply the theme
vim.cmd("hi clear")
if vim.fn.exists("syntax_on") then
  vim.cmd("syntax reset")
end

-- Set the colorscheme name
vim.g.colors_name = "mytheme"
vim.o.termguicolors = true
vim.o.background = "dark"

print("🎨 Custom theme 'mytheme' loaded successfully!")

This template gives you a fully functional colorscheme. You can iterate on the palette and highlight definitions to create exactly the visual experience you want. Place this file in your colors directory and load it with :colorscheme mytheme.

Advanced Customization: Transparent Backgrounds and Blend Effects

Transparent backgrounds are a popular aesthetic choice, especially for users who want their terminal emulator's background image or desktop wallpaper to show through. Neovim supports transparency through the blend property and by setting background colors to "none" or "NONE":

-- Transparency configuration snippet
-- Add this to your colorscheme or highlight override file

-- Method 1: Set specific groups to transparent
vim.api.nvim_set_hl(0, "Normal", { bg = "NONE", fg = "#c0caf5" })
vim.api.nvim_set_hl(0, "NormalFloat", { bg = "NONE" })
vim.api.nvim_set_hl(0, "SignColumn", { bg = "NONE" })
vim.api.nvim_set_hl(0, "LineNr", { bg = "NONE", fg = "#4c526b" })
vim.api.nvim_set_hl(0, "CursorLineNr", { bg = "NONE", fg = "#ff9e64" })
vim.api.nvim_set_hl(0, "EndOfBuffer", { bg = "NONE", fg = "NONE" })
vim.api.nvim_set_hl(0, "VertSplit", { bg = "NONE", fg = "#3b4261" })

-- Method 2: Use blend for floating windows (0-100, 0 = fully transparent)
vim.api.nvim_set_hl(0, "Pmenu", { bg = "#1e222a", blend = 15 })
vim.api.nvim_set_hl(0, "NormalFloat", { bg = "#1e222a", blend = 10 })
vim.api.nvim_set_hl(0, "FloatBorder", { bg = "NONE", blend = 0 })

-- Method 3: Set window-local transparency with winblend
vim.opt.winblend = 10  -- Applies to all floating windows globally

-- For per-window control:
-- vim.api.nvim_win_set_option(win_id, "winblend", 15)

Not all terminal emulators support true transparency. You need a compositor-aware terminal (like Kitty, Alacritty, or WezTerm) with background opacity configured. If colors look opaque despite setting bg = "NONE", verify your terminal's opacity settings.

Integrating Lualine with Custom Themes

Lualine is the most popular statusline plugin due to its flexibility. It can auto-detect your colorscheme or accept explicit customization. Here's how to create a deeply customized lualine configuration that matches your personal theme:

-- Advanced lualine configuration with custom components
-- Place in your lazy.nvim plugin spec or a separate config file

local function lualine_config()
  -- Custom component: Show LSP client name
  local function lsp_clients()
    local clients = vim.lsp.get_clients()
    if #clients == 0 then return "" end
    local names = {}
    for _, client in ipairs(clients) do
      table.insert(names, client.name)
    end
    return "ο‚£  " .. table.concat(names, ", ")
  end

  -- Custom component: Show git blame (requires gitsigns.nvim)
  local function git_blame()
    if not package.loaded["gitsigns"] then return "" end
    local blame = require("gitsigns").blame_line
    local result = blame and blame()
    return result and " " .. result.author or ""
  end

  -- Custom component: Macro recording indicator
  local function macro_recording()
    local reg = vim.fn.reg_recording()
    if reg == "" then return "" end
    return "  Recording @" .. reg
  end

  return {
    options = {
      icons_enabled = true,
      theme = "auto",
      component_separators = { left = "", right = "" },
      section_separators = { left = "ξ‚΄", right = "ξ‚Ά" },
      disabled_filetypes = {
        statusline = { "alpha", "dashboard", "NvimTree", "toggleterm" },
      },
      always_divide_middle = true,
      globalstatus = false,
    },
    sections = {
      lualine_a = {
        {
          "mode",
          fmt = function(str)
            local icons = {
              NORMAL = "",
              INSERT = "",
              VISUAL = "",
              ["VISUAL LINE"] = "",
              COMMAND = "",
              TERMINAL = "ο’ž",
            }
            return icons[str] or str
          end,
        },
      },
      lualine_b = {
        {
          "branch",
          icon = "",
          color = { fg = "#ff9e64" },
        },
        {
          "diff",
          symbols = {
            added    = "ο‘— ",
            modified = "ο‘™ ",
            removed  = "ο‘˜ ",
          },
          colored = true,
        },
        {
          "diagnostics",
          sources = { "nvim_lsp" },
          sections = { "error", "warn", "info", "hint" },
          symbols = {
            error = " ",
            warn  = " ",
            info  = " ",
            hint  = " "

β€” Ad β€”

Google AdSense will appear here after approval

← Back to all articles