doc/ui.txt — rendered from the plugin's own vimdoc
*ui.txt* Statusline, tabline and theme layer for Neovim Author: Stefan Bartl License: MIT
CONTENTS
1. Introduction ........................ |ui-introduction| 2. Requirements ........................ |ui-requirements| 3. Setup ............................... |ui-setup| 4. Commands ............................ |ui-commands| 5. Keymaps ............................. |ui-keymaps| 6. Statusline variants ................. |ui-variants| 7. Tabline styles ....................... |ui-tabline-styles| 8. Health .............................. |ui-health|
1. INTRODUCTION
ui.nvim is the frame around the window: statusline, tabline and theme
assembly.
The dividing line this plugin draws is worth stating plainly:
Content lives inside the window. ui.nvim paints the frame around it.
Cursorline, mode tinting, indent guides and occurrence highlighting are
content; they work with any statusline and are out of scope here. Statusline,
tabline and theme assembly are frame.
2. REQUIREMENTS
Neovim 0.10+
lib.nvim required
NvChad not required by this plugin's own code (v2.5, if present)
As of roadmap step 6, this plugin's own code no longer needs NvChad OR base46
to load, assemble, render, move buffers and tabs, or switch themes:
ui.statusline.render is the vim.o.statusline / generate() walk that
used to be entirely nvchad.init + nvchad.stl.utils.generate() (step 4;
step 3 had already ported the primitives and separator reads that walk
itself uses), ui.bindings.keymaps.tabufline.state is the vim.t.bufs
bookkeeping and close_buffer()/move_buf() that used to be entirely
nvchad/tabufline/lazyload.lua + nvchad.tabufline (step 5), and
ui.theme.palette/ui.theme.transparency/ui.bindings.usrcmds.themes
replace base46 (step 6): accent colors come from the active colorscheme's
own highlight groups, transparency is this plugin's own toggle, and theme
switching is a real :colorscheme call. Verified headless, with neither
NvChad nor base46 on the runtimepath: all four statusline presets assemble
and render end to end, buffer/tab navigation works against real buffers and
tabs, and theme/transparency switching works against a real colorscheme.
As of roadmap step 7 (2026-09-13), that is also true of a real install: the
reference host this plugin was extracted from calls `ui.statusline.render
.enable()` from its own startup, and NvChad is not installed there any more
at all. This plugin covers statusline/tabline/theme only, though -- it does
not replace whatever else a distribution bundle also installed (Mason,
which-key, Treesitter, Telescope, and so on all needed their own separate
plugin specs in that host once NvChad's own bundle stopped providing them).
Soft, each blanking only its own statusline segment: nvim-web-devicons,
gitsigns.nvim, casedesk.nvim, filetree.nvim, github_stats.nvim,
runtime-analysis.nvim, recommender.nvim, sessions.nvim, sandbox.nvim,
lazy.nvim. :checkhealth ui reports each of them separately; the full
table with links is docs/requirements.md.
3. SETUP
Two entry points, at different times.ui.config.setup()runs from your own config's startup, whenever you want the frame drawn -- no distribution hook needed or expected. It only assembles a config table;enable()on each renderer is the separate step that actually pointsvim.o.statusline/vim.o.tablineat it:
-- Anywhere in your own startup, once (the reference host's own shape,
-- config/ui_statusline/init.lua -- adapt names, not structure):
local ok, assembled = pcall(require("ui.config").setup, {
theme = { theme_toggle = { "rosepine", "tokyonight" } },
})
if ok then
require("ui.statusline.render").enable(assembled.ui.statusline)
require("ui.tabline.render").enable(assembled.ui.tabline)
end
ui.setup(opts) runs afterwards, from your own config, and answers "which
keymaps and commands exist":
require("ui").setup({ all = true })
Nothing is on by default -- the two halves are independently useful, so the flags are explicit:
require("ui").setup({ usrcmds = true }) -- commands only, no keymaps
all (boolean) shorthand for every flag below
keymaps (boolean) buffer/tab navigation and tabline mappings
usrcmds (boolean) the :UI command and theme management
Theme overrides go to ui.config.setup, not here:
require("ui.config").setup({
theme = { theme_toggle = { "rosepine", "tokyonight" }, transparency = true },
})
4. COMMANDS
*:UI* :UI theme {name} Switch to a colorscheme (:colorscheme {name}). {name} completes over every colorscheme Neovim can see, read at the moment <Tab> is pressed. :UI themes List available themes, marking the active one. :UI picker Open a floating theme picker (via lib.nvim'sui.kit.select): the highlighted theme applies live as the cursor moves over the list,<CR>keeps it,<Esc>/q/closing the float any other way restores whatever theme was active before the picker opened. :UI toggle Swap between the two themes intheme.theme_toggle. :UI transparency Toggle background transparency. :UI variant {name} Switch the active statusline preset at runtime. {name} completes over the variant registry (see |ui-variants| below): the four shipped presets, plus anything a host registered under its own name. :UI variants List registered variants, marking the active one. :UI tabline-style {name} Switch the active tabline chip-boundary look at runtime. {name} completes over the tabline-style registry (see |ui-tabline-styles| below): the three shipped looks, plus anything a host registered under its own name. :UI tabline-styles List registered tabline styles, marking the active one. :UI modules List every catalogued statusline segment (built into the "default" theme, or a standalone module), what each shows, what it needs, and which shipped presets already use it. Same data as docs/modules.md. :UI status Current theme, transparency state, statusline variant and tabline style. :UI help The subcommand list, in a float. No subcommand takes a range or a count: each acts on global state, where neither has a meaning.
5. KEYMAPS
Registered byui.setup({ keymaps = true }). <Tab> next buffer <S-Tab> previous buffer <leader>bc close the current buffer (or {count} of them), keeping the window layout -- an uncounted close flashes the chip first; {count} > 1 closes immediately, unflashed <leader>bq close every listed buffer in the current tab -- all flash together first, then close as one batch <leader>tr move the current buffer right in the tabline <leader>tl move it left <leader>tt move the current buffer into a new tab <leader>ut toggle between the two themes in theme.theme_toggle <leader>uP open the visual theme picker (live preview) Every one is wrapped: a failure notifies and returns rather than raising, because these sit on keys pressed constantly and a traceback out of <Tab> makes the editor feel broken.<leader>trand<leader>tlgo throughui.bindings.keymaps.tabufline.state(own code, step 5, notnvchad.tabufline) -- they work with NvChad entirely absent. *ui-tabline-mouse*
Tabline mouse
The tabline's own click protocol reports the button, so these are not keymaps and need nokeymaps = true: left click switch to the chip's buffer left drag carry the chip along the bar; it re-slots live right click that tab's context menu (also on a chip's "x") middle click close the chip click on "x" close the chip The tab menu (ui.tabline.menu) holds only actions on that tab: Save (modified only), Close, Close others / to the left / to the right / saved, Move to position... (3absolute,+2/-1relative), Move left / right / to start / to end, Copy path, Open in split / vertical split, Move to new tab page.context_menu,dragandmiddle_click_closeon the tabline config each turn one gesture off (back to a plain switch). A host's own global<RightMouse>mapping keeps working if it replays the native click (normal! <RightMouse>) and then returns whenrequire("ui.tabline.menu").pointer_on_tabline()is true. See docs/BINDINGS.md for the snippet.
6. STATUSLINE VARIANTS
Four generic presets ship. Which one is assembled at boot is the STATUSLINE_VARIANT constant inlua/ui/config/init.lua-- but since this plugin owns its own render entrypoint (step 4), that is no longer the only chance::UI variant {name}switches it at runtime. default full-featured, closest to the historical NvChad default minimal cursor, working directory, progress -- nothing else lsp LSP-aware breadcrumbs plus the enhanced segments blocks "lsp"'s segments, drawn as gen_block chips An unknown name falls back todefaultwith a notification rather than throwing.|:checkhealth|reports the boot-time default, whether it is registered, and the actually active one (ui.config.get_variant()) separately -- they can differ after a runtime switch. This used to be six presets;custom(personal-plugin-coupled: casedesk.nvim, filetree.nvim) moved to docs/examples/personal-statusline-example.lua instead of staying a shipped preset, andlspbased/custom_light(the same segment set assembled two different ways) became one file,lsp.ui.config.variantsis the registry both the four shipped presets and any host-registered variant live in -- what:UI variant's completion andui.config.setup({ variant = "name" })both read:
require("ui.config.variants").register("personal", require("your_config.statusline"))
require("ui.config").setup({ variant = "personal" })
opts.variant also still accepts a table directly, used anonymously without
a name to register:
require("ui.config").setup({
variant = require("your_config.statusline"),
})
7. TABLINE STYLES
Unlike a statusline variant (a whole {order, modules} table), the tabline
has one shipped config -- cfg.style is a single field of it, picking the
chip-boundary look between buffer chips:
rounded (default) a cap on every internal boundary; square only where
the visible run actually meets an edge
square nothing added; chips sit flush against each other
divider one plain vertical bar per internal boundary, no rounding
An unrecognized or unset name falls back to rounded rather than throwing.
ui.tabline.styles is the registry both the three shipped looks and any
host-registered style live in -- what :UI tabline-style's completion
reads, and what cfg.style resolves through on every redraw:
local styles = require("ui.tabline.styles")
styles.register("my_style", function(chips, chip_bufs, cur, flush_right)
-- mutate `chips` in place; see ui.tabline.styles's own doc comment
-- for what each parameter is
end)
require("ui.config").setup({ tabline = { style = "my_style" } })
:UI tabline-style {name}switches it at runtime without a fullui.config.setup()round-trip: it mutatescfg.styleon the already-` enable()d tabline config directly, then:redrawtabline` makes it visible.
8. HEALTH
:checkhealth ui
Eight sections: dependencies (lib.nvim required; NvChad presence reported as information, not a dependency check, since step 4;nvchad.tabuflinegone from this list entirely at step 5,base46/base46.themesgone the same way at step 6), this plugin's own statusline render entrypoint (resolves? renders the "default" theme's fallback modules without NvChad?), the tabline render entrypoint (same shape, one option later), the assembled configuration, which submodulessetup()turned on (buffer/tab navigation included, underkeymaps), the soft dependencies each statusline segment needs, whetherui.winbarresolves for a content plugin to contribute its breadcrumb line to, and which optional integrations currently resolve. That last section lists the foreign modules this plugin probes (seeui.util.soft_require). A missing one is never an error -- standalone is the ordinary case and the matching segment renders empty by design -- but the list is printed because a soft dependency that disappears upstream otherwise disables a feature in complete silence. The dependency section returns early only if lib.nvim itself is missing or incomplete -- everything below it needs lib.nvim, and a missing NvChad no longer stops the report (it stopped being fatal at step 4).
RIGHT-CLICK MENU
ui.setup({ menu = { ... } })(orrequire("ui.menu").setup()) binds<RightMouse>(menu at the pointer);key = "<A-b>"adds the same menu at the cursor. Off until asked for. It shows a fly-out per installed sister plugin, the general sections Code / Clipboard / File / Delete / Tools, and rows of your own (extra). A sister plugin appears when it is installed,menu.integrations.<name>is not false, and the plugin has not switched itself off (itssubmenu()returns nil; the recommended switch name isintegrations.ui_menu = false). Delete All / Delete File are off unless enabled inmenu.entries. "Copy/Delete Marked" act on the Visual selection captured when the menu opened. See lua/ui/menu/README.md.