hyprfade.nvim

September 23, 2026 · View on GitHub

Seamlessly fade the terminal window hosting Neovim on Hyprland by setting its window opacity via the Hyprland IPC

https://github.com/user-attachments/assets/bb2bf4d1-1335-45f4-9c7e-646201cca975

Why

Lets you toggle the opacity of the terminal window from inside Neovim, so you can fade it while editing and bring it back to opaque whenever you like.

Hyprland window rules based on class or title can miss the terminal window when Neovim is launched from a file manager like yazi, because the window's class hasn't been resolved yet. Matching on pid: sidesteps this entirely.

Requirements

  • Hyprland 0.53.0+ (uses the opacity / opacity_inactive setprop props; older Hyprland versions used alpha / alphainactive instead and aren't supported)
  • hyprctl on PATH

Install

lazy.nvim

{
  "Senal-D-A-Gunaratna/hyprfade.nvim",
  lazy = false,
  priority = 1000,
  opts = {
    opacity = 0.7,           -- opacity for active windows
    opacity_inactive = 0.75,  -- opacity for inactive windows
    term_names = {               -- process names to recognise as terminals
      "alacritty", "foot", "ghostty", "kitty", "wezterm",
    },
  },
  keys = {
    { "<leader>uo", "<cmd>HyprfadeToggle<cr>", desc = "Toggle window opacity" },
  },
},

Configuration

An opts table must be passed to setup(). There are no built-in defaults.

require("hyprfade").setup({
  opacity = 0.7,              -- opacity for active windows (required, 1 - 0.0)
  opacity_inactive = 0.75,  -- opacity for inactive windows
  term_names = {                -- process names to recognise as terminals (required)
    "alacritty", "foot", "ghostty", "kitty", "wezterm",
  },
})
OptionTypeDescription
opacitynumberOpacity value for active windows
opacity_inactivenumberOpacity for inactive windows
term_namestableProcess names to recognise

Opacity is always applied as soon as setup() runs, and always reset to fully opaque (1) on VimLeavePre — these aren't configurable. If hyprctl isn't on PATH, every entry point (setup(), Hyprfade, HyprfadeToggle, HyprfadeReset) no-ops and notifies via vim.notify rather than erroring, so it's safe to load the plugin unconditionally even outside a Hyprland session (e.g. nvim over SSH, or on X11/another compositor). If the terminal pid can't be resolved from the process tree, the plugin falls back to the focused window via the Hyprland IPC (matching its class against term_names). If opacity is missing or invalid, setup() notifies at ERROR level and stops — the plugin won't activate.

Health check

:checkhealth hyprfade reports whether the terminal was resolved by walking the /proc process tree (PID detection) or by the focused-window fallback, so you can quickly confirm which path is active and whether term_names matches your terminal. The status is tracked per-session in vim.g.hyprfade_used_fallback.

Commands

CommandDescription
Hyprfade [value]Set opacity to a value (1 - 0.0)
HyprfadeToggleToggle between 1 and opts opacity
HyprfadeResetReset opacity to 1 (fully opaque)

How opacity is actually applied

Hyprland 0.55 (May 2026) replaced the classic hyprctl dispatch <name> <args...> calling convention with a Lua expression API — the old space-separated dispatch setprop pid:X opacity 0.5 form is rejected outright with a Lua syntax error on 0.55+. The plugin uses the current typed form instead, batched into a single hyprctl eval call:

hl.dispatch(hl.dsp.window.set_prop({
  prop = "opacity_override",
  value = 1,
  window = "<pid:... or address:0x...>",
}))
hl.dispatch(hl.dsp.window.set_prop({
  prop = "opacity",
  value = <value>,
  window = "<selector>",
}))
hl.dispatch(hl.dsp.window.set_prop({
  prop = "opacity_inactive_override",
  value = 1,
  window = "<selector>",
}))
hl.dispatch(hl.dsp.window.set_prop({
  prop = "opacity_inactive",
  value = <value>,
  window = "<selector>",
}))

The window selector is pid:<pid> when the terminal is found by walking the process tree, and address:0x... when it isn't (see below).

Both opacity and opacity_inactive are set (not just opacity), because Hyprland resets opacity to 1.0 the moment the window loses focus if only the active-state prop is overridden. Each value requires its matching *_override flag set to 1, or Hyprland ignores it. HyprfadeReset sets opacity to 1 (fully opaque); VimLeavePre also sets opacity to 1 so the terminal is fully opaque when Neovim exits

If the terminal PID can't be resolved from the process tree, the plugin asks the Hyprland IPC for the currently focused window (hyprctl activewindow -j) and targets it by address instead. The same opacity / opacity_inactive pair is applied, so the fade works even when the parent chain reparented (e.g. detached spawns). The focused window is only matched when its class is one of term_names, so an unrelated window is never dimmed. When the fallback kicks in you'll get a vim.notify at INFO level (e.g. "terminal PID not found; using focused window"), once per session — the resolved selector is cached, so it won't repeat on exit or on later Hyprfade calls.

Opacity is applied immediately when setup() runs, rather than waiting for a VimEnter autocmd. This matters for lazy-loaded installs: lazy.nvim's VeryLazy event (used in the install snippet above) fires after VimEnter has already completed for the session, so a VimEnter autocmd registered inside setup() would never fire

Known limitations

PID resolution walks the /proc tree upwards from Neovim's PID through the parent chain (up to 25 hops). If the ancestor chain reparents to PID 1 before hitting a known terminal name (e.g. some detached spawn paths), resolution fails and the plugin falls back to the active window. If the active window query also fails (or its class isn't a known terminal), a warning is logged via vim.notify and no opacity is applied. When the fallback succeeds you get an INFO level notification naming the window it resolved to.