# fidget.nvim: a corner window that manages its own notifications

> A Lua plugin that turns Neovim's LSP progress handler and vim.notify into a small self-destructing window, with a large options table and a major rewrite in version 2.0.0.

**j-hui/fidget.nvim** — 💫  Extensible UI for Neovim notifications and LSP progress messages.

- Repository: https://github.com/j-hui/fidget.nvim
- Stars: 2,596 · Forks: 83
- Language: Lua
- License: MIT
- Published: 2026-10-07 · Updated: 2026-10-07 · Language: en
- Canonical page: https://hysenlabs.com/projects/j-hui-fidget-nvim

## A window that owns its own lifetime

fidget.nvim is a Lua plugin with one stated job: give Neovim's language server progress messages somewhere to go that is not the status line. The README's Why section is unusually honest about the motivation. There is only so much information one can stash into the status line, it says, and it would not mind a little terminal decoration as a treat.

The mechanics are narrower than that framing suggests. Fidget provides a UI for Neovim's `$/progress` handler, and it provides a configurable `vim.notify()` backend. Those are two separate surfaces, and a plugin can be useful with only one of them. You can route your own notifications through it without any language server configured at all, which is what the demo does: the recording shows a Rust file being opened, `rust-analyzer` sending progress messages, and then four keymaps firing notifications on demand.

```lua
local fidget = require("fidget")

vim.keymap.set("n", "A", function()
  fidget.notify("This is from fidget.notify().")
end)

vim.keymap.set("n", "B", function()
  fidget.notify("This is also from fidget.notify().", vim.log.levels.WARN)
end)
```

The demo file notes the normal-mode choice is deliberate: ex mode pauses Fidget rendering, which makes a recording look glitchy. That is a small thing to know, and it tells you the plugin is drawing on a timer.

There is a third goal in the list that is less about function than feel: ASCII spinner animations, offered as a sign of life while work is in flight. The `progress_icon` option defaults to the `dots` set, which is the conventional three-frame spinner, and the option accepts a table so you can substitute your own.

## Installing with lazy.nvim, or calling setup yourself

The requirements section names one hard floor: Neovim v0.11.3. Older versions may work, the README says, but full functionality is not guaranteed. For progress messages you also need a language server actually using the `$/progress` handler, and the plugin keeps a wiki page listing servers known to work.

The lazy.nvim installation is the shortest of the three documented paths, because the options table can be handed over inline:

```lua
{
  "j-hui/fidget.nvim",
  opts = {
    -- options
  },
}
```

vim-plug needs a separate call after `plug#end()`:

```vim
Plug 'j-hui/fidget.nvim'

" Make sure the plugin is installed using :PlugInstall. Then, somewhere after plug#end():
lua <<EOF
require("fidget").setup {
  -- options
+}
EOF
```

The third path is rocks.nvim, which is a single command: `:Rocks install fidget.nvim`. The plugin also carries a LuaRocks badge, so the rocks route is a first-class install rather than an afterthought.

The versioning note deserves more attention than it gets. Fidget is developed on `main` and the README says it may occasionally undergo breaking changes. If you want configuration stability, pin a tag, and the example given is a wildcard version string with a comment offering a concrete alternative such as `1.6.1`. For a plugin whose main selling point is a configurable options table, a major release that renames options is the failure mode you are actually protecting against.

## Reading the options table as the real interface

Fidget is configured by passing a table to `setup()`, and the README prints the defaults with comments attached to every field. That makes the table double as the documentation, which is more useful than a prose reference would be. The top level splits into `progress` for the language server side and `notification` for the general purpose side, and the progress block is where the interesting decisions sit.

Filtering happens before display. `ignore` takes a list of language servers to skip outright. `ignore_done_already` drops tasks that arrive already complete, and `ignore_empty_message` drops tasks carrying no message, which are frequently noise from servers that signal a phase change without describing it. `suppress_on_insert` keeps new messages out while you are typing. None of these are the kind of option you would guess at, and they are the ones that decide whether the window feels quiet or noisy in daily use.

`clear_on_detach` is a function rather than a flag, and the default is instructive: it looks up the client by id and returns its name or nil, meaning the group is only cleared when the server detaches if a name comes back. `notification_group` is also a function, defaulting to `msg.lsp_client.name`, so grouping is per server out of the box. Making both callable is the design decision that lets the window key on something other than a fixed string.

Display options are where you get the feel of the thing. `render_limit` caps how many messages show at once, at sixteen by default. `done_ttl` is three seconds, so a completed task lingers briefly and then leaves, while `progress_ttl` is `math.huge`, meaning in-flight work stays until it finishes. `done_icon` and `progress_icon` set the glyphs, `done_style`, `progress_style`, `group_style` and `icon_style` map onto existing highlight groups such as `Constant`, `WarningMsg`, `Title` and `Question`, and `priority` at 30 orders the window against other floating windows. `skip_history` keeps progress chatter out of `:messages`, which is a real quality-of-life detail. Formatting is three separate functions: `format_message`, `format_annote` and `format_group_name`.

The `overrides` table is per-server, and the shipped example is `rust_analyzer = { name = "rust-analyzer" }`. That is the extension point for servers whose progress reports carry a prefix you do not want in the title.

## What version 2.0.0 removed, capped and reflowed

The v2.0.0 tag was published on 2026-06-21 and it is a breaking release with a short, specific list of changes. Two integrations were removed outright: nvim-tree and xcodebuild-nvim. Positioning the window stopped consulting configurable filetypes, so the window no longer changes placement based on what kind of buffer is open.

The more substantive half is about text layout. The maximum window width is now capped at 0.3 of the editor, and messages that exceed that width are reflowed, with smart reflow and hyphenation support. A `line_margin` option was added to pad lines with spaces. There is also a fix for rendering items whose text is empty but whose annotation is not, which is a nice detail for servers that send progress with the useful part in the annotation rather than the message.

Compare that with v1.6.0 and v1.6.1, both from February 2025. v1.6.0 added a Telescope extension, and v1.6.1 fixed a glob pattern in the LuaRocks CI workflow. The gap between the two February releases and the June major is about sixteen months, and it lines up with the README raising the requirement to Neovim v0.11.3. The repository's last push was on 2026-09-03, so the current line is still moving.

## Tests, docs and the justfile

The repository tree is small and legible: `lua/` for the plugin, `doc/` for the generated help, `tests/` for the suite, `scripts/` for doc builders, plus a `justfile`, a `stylua.toml` and a `CHANGELOG.md`. There is no vendored dependency directory and no compiled component, which is what you expect from a plugin distributed through a plugin manager.

The justfile is the most direct evidence of how the project is developed. Tests run headless against a minimal init:

```
test:
    nvim --clean --headless -u tests/minimal_init.lua -c "PlenaryBustedDirectory tests/ { minimal_init = 'tests/minimal_init.lua' }"
```

That is Plenary's busted directory runner driven through a headless Neovim, and `tests/minimal_init.lua` is the harness that loads only what the suite needs. The same file also declares recipes for rendering API documentation and options documentation through shell scripts in `scripts/`, which explains why the README's options table can stay generated rather than drifting from the code.

The docs setup is worth noting too. The README badge points at `doc/fidget.txt`, and the options section is fenced off from the rest of the file by panvimdoc ignore markers, which is how a single options table ends up in both the repository README and the Vim help file without being maintained twice.

## Where the README ends and the wiki starts

The README covers installation, requirements, versioning and the full default options table, which is more than most plugin READMEs manage. What it does not do is answer the compatibility question. The link for that goes to a wiki page titled known-compatible LSP servers, and the requirement that you must have a server using `$/progress` means that page is the first stop for anyone whose progress window stays empty.

The README also does not document the internals. The `lua/` directory is where they live, and given the size of the options table the mapping from option to implementation is best read there rather than guessed at from the README.

Two numbers frame expectations. The repository has 2,596 stars, 83 forks and 16 open issues, which is a healthy ratio for a plugin with a single maintainer. The description itself is one line: an extensible UI for Neovim notifications and LSP progress messages. It is accurate, and it is narrower than the feature set, since the notification backend works with no language server in the picture at all.

## Conclusion

fidget.nvim earns its place if your editor spends real time waiting on a language server, because it answers the question the status line cannot: which task is running, for how long, and is it done. The options table is where the work is, and it is a big one, so treat reading `progress.display` and the notification defaults as part of installation rather than something to do later. The 2.0.0 release moved the window to a fixed 0.3 maximum width with hyphenated reflow and dropped the nvim-tree and xcodebuild integrations, which is why pinning a tag matters if your config calls those entry points. Start with the lazy.nvim block, run the test command from the justfile if you plan to patch it, and read the known-compatible LSP servers wiki page, which is where the plugin's own answer to the compatibility question lives.

## FAQ

### Do I need a language server for fidget.nvim to do anything?

No. Fidget has two separate surfaces: a UI for the language server progress handler, and a configurable `vim.notify()` backend. With no language server configured you still get the notification window, which is how the README demo triggers its visible output through four keymaps calling `fidget.notify()`.

### What is the minimum Neovim version fidget.nvim needs?

The requirements section names Neovim v0.11.3. The plugin may work on older versions, but the README is explicit that full functionality is not guaranteed.

### Why did my LSP progress window stay empty?

Three things can cause it. Your server may not use the `$/progress` handler at all. Your `ignore` list may name it. Or it may be sending tasks with no message text, which the `ignore_empty_message` option is designed to drop. The plugin keeps a wiki page listing language servers known to work, which is the fastest way to rule out the first case.

### What changed in fidget.nvim version 2.0.0?

It is a breaking release. The nvim-tree and xcodebuild-nvim integrations were removed, the window stopped positioning itself from configurable filetypes, the maximum width was capped at 0.3 of the editor, and overlong messages are now reflowed with hyphenation support. A `line_margin` option was added, and items with empty text but a non-empty annotation now render.

### How do I stop fidget.nvim from showing messages while I type?

Set `suppress_on_insert` to true in the `progress` block. Neighboring options cover the same kind of quiet: `ignore_done_already` drops tasks that arrive already finished, `ignore_empty_message` drops ones with no text, and `skip_history` keeps progress chatter out of `:messages`.

## Sources

- [Issues](https://github.com/j-hui/fidget.nvim/issues)
- [j-hui/fidget.nvim on GitHub](https://github.com/j-hui/fidget.nvim)
- [License: MIT](https://github.com/j-hui/fidget.nvim/blob/main/LICENSE)
- [README](https://github.com/j-hui/fidget.nvim/blob/main/README.md)
- [Releases](https://github.com/j-hui/fidget.nvim/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/j-hui-fidget-nvim
