NORMAL ~/wkd/p/ui/help :set skin=modern utf-8

ui.txt

Statusline, tabline and theme layer for Neovim — ui.nvim

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 *ui-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-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 *ui-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 *ui-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 points vim.o.statusline/vim.o.tabline at 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-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's ui.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 in theme.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 *ui-keymaps*

Registered by ui.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>tr and <leader>tl go through ui.bindings.keymaps.tabufline.state
(own code, step 5, not nvchad.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 no keymaps = 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... (3 absolute, +2/-1 relative), Move left / right /
to start / to end, Copy path, Open in split / vertical split, Move to new tab
page. context_menu, drag and middle_click_close on 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 when
require("ui.tabline.menu").pointer_on_tabline() is true. See
docs/BINDINGS.md for the snippet.

6. STATUSLINE VARIANTS *ui-variants*

Four generic presets ship. Which one is assembled at boot is the
STATUSLINE_VARIANT constant in lua/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 to default with 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, and lspbased/custom_light (the same segment
set assembled two different ways) became one file, lsp.

ui.config.variants is the registry both the four shipped presets and any
host-registered variant live in -- what :UI variant's completion and
ui.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 *ui-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 full
ui.config.setup() round-trip: it mutates cfg.style on the already-`
enable()d tabline config directly, then :redrawtabline` makes it visible.

8. HEALTH *ui-health*

    :checkhealth ui
Eight sections: dependencies (lib.nvim required; NvChad presence reported as
information, not a dependency check, since step 4; nvchad.tabufline gone
from this list entirely at step 5, base46/base46.themes gone 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 submodules setup() turned on (buffer/tab navigation
included, under keymaps), the soft dependencies each statusline segment
needs, whether ui.winbar resolves 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 (see
ui.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-menu*

ui.setup({ menu = { ... } }) (or require("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 (its submenu() returns nil;
the recommended switch name is integrations.ui_menu = false). Delete All / Delete File are off unless enabled in
menu.entries. "Copy/Delete Marked" act on the Visual selection captured when
the menu opened. See lua/ui/menu/README.md.

keys

j / k
next / previous line
gg / G
first / last line
⏎
open the line under the cursor
/
search the plugins
:
command line — Tab completes
:help x
vimdoc of a plugin (:e x = plugin page)
:ls · :log · :stack
plugin list · activity stream · dependency graph
:colo x
colorscheme
:set skin=
modern | tui
?
this help
esc
close